openvibers/openvibe.contracts Commits
Define Run job contracts with non-JWT sample stream tickets
Changes
@@@ -4,6 +4,32 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `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.90.0 — 2026-10-04++++**OpenVibe.Run service and job API** (plan T14 R1, ADR-034). The contract OpenVibe.Run is built against; everything is++planned (Run's repository does not exist yet) and additive.++- New service manifest `manifests/services/run.json`: `run`, status and exposure `placeholder`, domain `openvibe.run`,++ lifecycle all `none` (not built). `manifests/products/openvibe.run.json` gains `relationships.service: run` and keeps++ `noRepo: true`.++- New planned capabilities, owner `run`, each with an input and an output schema: `run.job.submit` (sensitive: it bills++ the project), `run.job.read`, `run.job.list`, `run.job.cancel`, `run.job.stream` (public, project-scoped) and++ `run.job.admin` (internal operator access to every project's jobs). `run.admin` is spelled `run.job.admin` because a++ capability id has three segments.++- New contracts under `contracts/run/`: `run.job-create-request@1` (class from `platform.runtime-class@1`, artifact++ required for `function` and `code`, `inputs` as Media objects pinned by sha256, `limits` wall/cpu/mem as++ `platform.job@1`, `ttl_ms`, `egress` `none | public | openvibe-only` as Node's `worker.egress`,++ `requirements` as `platform.workload-requirements@1`, `idempotency_key`), `run.job-create-result@1`,++ `run.job-read-result@1` (states `queued | placed | running | succeeded | failed | cancelled | expired`, placement node,++ timings, exit, `usage.seconds`, result, error, with per-state rules), `run.job-list-query@1`, `run.job-list-result@1`++ (`next_cursor` pages), `run.job-stream-ticket-result@1` (one-use, two-minute ticket, as Events' realtime ticket) and++ `run.job-stream-event@1` (SSE `output | state | end`).++- New planned events produced by Run: `run.job.queued`, `run.job.started`, `run.job.succeeded`, `run.job.failed`,++ `run.job.cancelled` and `run.job.expired`, projections of the job without args, inputs, result or output.++- `ids.newId('job')` mints `job_<ULID>`, the `platform.job@1` id Run passes to the node, so the metering key stays++ `run:<job id>:<n>`.++- Fixtures: valid and invalid for every new contract; `test/platform-run.test.js` pins the Run job id, artifact,++ limits and egress to `platform.job@1` and Node, and `usage.seconds` to the sum of the job's readings.++## 0.89.0 — 2026-10-03**Agent tokens and confirmations** (plan T2 step 9; OpenVibe.Network `docs/t2-projects-and-grants.md` §2 gaps 1–3,@@@ -216,6 +216,27 @@ Each service manifest has a `lifecycle` block, filled from the service's code an`ovhost validate <service>` (OpenVibe.Host) reads the block from the inventory or from this package's manifest. It fails when a field is missing, when `deadlineSeconds` exceeds the unit's TimeoutStopSec or the unit's KillSignal is a different signal, and when the checkout's installed openvibe-contracts is outside `contracts.range`.++## Run job API (v0.90, planned)++++OpenVibe.Run (`manifests/services/run.json`, a placeholder until the repository exists; plan T14 R1) takes jobs,++places each on a node that advertises `worker:<class>` and sends it as `platform.job@1`. Its routes, all planned:++++| Route | Capability | Body | Answer |++| --- | --- | --- | --- |++| `POST /api/v1/jobs` | `run.job.submit` | `run.job-create-request@1` | `run.job-create-result@1` (201, or 200 for an idempotent repeat) |++| `GET /api/v1/jobs` | `run.job.list` | `run.job-list-query@1` (query string) | `run.job-list-result@1` (cursor pages) |++| `GET /api/v1/jobs/:id` | `run.job.read` | — | `run.job-read-result@1` |++| `POST /api/v1/jobs/:id/cancel` | `run.job.cancel` | — | `run.job-read-result@1` |++| `POST /api/v1/jobs/:id/stream/ticket` | `run.job.stream` | — | `run.job-stream-ticket-result@1` (one-use, 120 s) |++| `GET /api/v1/jobs/:id/stream?ticket=` | `run.job.stream` | — | SSE of `run.job-stream-event@1` (`output`, `state`, `end`; Last-Event-ID resumes) |++| `GET /api/v1/admin/jobs` | `run.job.admin` (internal) | `run.job-list-query@1` with `project_id` | `run.job-list-result@1` |++++A job moves `queued → placed → running` and ends `succeeded`, `failed`, `cancelled` or `expired`; Run publishes++`run.job.queued|started|succeeded|failed|cancelled|expired`. Run mints the job id (`job_<ULID>`, `ids.newId('job')`)++and passes it unchanged as the `platform.job@1` id, so every metering reading is `run:<job id>:<n>` (service `run`,++operation `function.invoke`, unit `s`) and `usage.seconds` is their sum. Inputs are Media objects pinned by sha256++(never a URL); `egress` takes Node's `worker.egress` values (`none` by default, `public`, `openvibe-only`).++## Versioning and compatibility- A contract id is permanent. Minor versions only add optional fields.@@@ -5694,6 +5694,136 @@"visibility": "first-party","compatibility": "backward","status": "active"++ },++ {++ "id": "run.job-create-request",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-create-request.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-create-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-create-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-read-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-read-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-list-query",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-list-query.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-list-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-list-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-stream-ticket-result",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-stream-ticket-result.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job-stream-event",++ "version": "1.0.0",++ "owner": "run",++ "schema": "run/job-stream-event.v1.json",++ "visibility": "public",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.queued",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.queued.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.started",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.started.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.succeeded",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.succeeded.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.failed",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.failed.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.cancelled",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.cancelled.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"++ },++ {++ "id": "run.job.expired",++ "version": "1.0.0",++ "owner": "run",++ "schema": "events/payloads/run.job.expired.v1.json",++ "visibility": "first-party",++ "compatibility": "backward",++ "status": "planned",++ "adr": "ADR-034"}]}nnew file mode 100644@@@ -0,0 +1,115 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.cancelled.v1.json",++ "title": "RunJobCancelledPayload",++ "description": "run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "cancelled",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,115 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.expired.v1.json",++ "title": "RunJobExpiredPayload",++ "description": "run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "expired",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/error"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,115 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.failed.v1.json",++ "title": "RunJobFailedPayload",++ "description": "run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "failed",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/error"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,92 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.queued.v1.json",++ "title": "RunJobQueuedPayload",++ "description": "run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "queued",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "null"++ },++ "started_at": {++ "type": "null"++ },++ "finished_at": {++ "type": "null"++ }++ }++ },++ "exit": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,94 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.started.v1.json",++ "title": "RunJobStartedPayload",++ "description": "run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "running",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": "null"++ }++ }++ },++ "exit": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,110 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.succeeded.v1.json",++ "title": "RunJobSucceededPayload",++ "description": "run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "succeeded",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "allOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "object",++ "properties": {++ "reason": {++ "const": "exited"++ },++ "code": {++ "const": 0++ }++ }++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,119 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-create-request.v1.json",++ "title": "RunJobCreateRequest",++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "class",++ "limits"++ ],++ "properties": {++ "class": {++ "$ref": "../platform/runtime-class.v1.json"++ },++ "artifact": {++ "$ref": "#/$defs/artifact"++ },++ "args": {++ "type": "object",++ "description": "The artifact's declared input as JSON; default {}."++ },++ "inputs": {++ "type": "array",++ "maxItems": 32,++ "items": {++ "$ref": "#/$defs/input"++ },++ "description": "Files placed in the job directory before the process starts; names are unique (else 422)."++ },++ "limits": {++ "$ref": "#/$defs/limits"++ },++ "ttl_ms": {++ "type": "integer",++ "minimum": 1000,++ "maximum": 86400000,++ "description": "Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000."++ },++ "egress": {++ "$ref": "#/$defs/egress"++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json"++ },++ "idempotency_key": {++ "$ref": "#/$defs/idempotency_key"++ }++ },++ "allOf": [++ {++ "if": {++ "required": ["class"],++ "properties": { "class": { "enum": ["function", "code"] } }++ },++ "then": {++ "required": ["artifact"]++ }++ }++ ],++ "$defs": {++ "artifact": {++ "type": "object",++ "additionalProperties": false,++ "description": "The pre-registered artifact to run (platform.job@1 artifact).",++ "required": ["name", "version"],++ "properties": {++ "name": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$" },++ "version": { "type": "string", "pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$", "description": "One exact version, never a range" }++ }++ },++ "input": {++ "type": "object",++ "additionalProperties": false,++ "description": "One input file: an OpenVibe.Media object the caller's project can read, pinned by digest.",++ "required": ["name", "media_id", "sha256"],++ "properties": {++ "name": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$", "description": "File name in the job directory; no path separators." },++ "media_id": { "type": "string", "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$" },++ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "Lowercase hex SHA-256 of the object's bytes." },++ "size_bytes": { "type": "integer", "minimum": 0 }++ }++ },++ "limits": {++ "type": "object",++ "additionalProperties": false,++ "description": "Caps on the running process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.",++ "required": ["wall_ms", "cpu_ms", "mem_bytes"],++ "properties": {++ "wall_ms": { "type": "integer", "minimum": 1, "description": "Running time from `job_started`" },++ "cpu_ms": { "type": "integer", "minimum": 1, "description": "CPU time, user plus system" },++ "mem_bytes": { "type": "integer", "minimum": 1, "description": "Peak resident memory" }++ }++ },++ "egress": {++ "type": "string",++ "enum": ["none", "public", "openvibe-only"],++ "default": "none",++ "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."++ },++ "idempotency_key": {++ "type": "string",++ "pattern": "^[A-Za-z0-9._:-]{1,128}$"++ }++ },++ "examples": [++ {++ "class": "function",++ "artifact": { "name": "thumbnail", "version": "1.2.0" },++ "args": { "width": 320 },++ "inputs": [++ { "name": "photo.jpg", "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ", "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "size_bytes": 482113 }++ ],++ "limits": { "wall_ms": 30000, "cpu_ms": 30000, "mem_bytes": 268435456 },++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-create-result.v1.json",++ "title": "RunJobCreateResult",++ "description": "run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created",++ "job"++ ],++ "properties": {++ "created": {++ "type": "boolean",++ "description": "false when the idempotency_key matched an earlier job."++ },++ "job": {++ "$ref": "job-read-result.v1.json"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-list-query.v1.json",++ "title": "RunJobListQuery",++ "description": "run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "state": {++ "enum": ["queued", "placed", "running", "succeeded", "failed", "cancelled", "expired"]++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."++ },++ "limit": {++ "oneOf": [++ { "type": "integer", "minimum": 1, "maximum": 200 },++ { "type": "string", "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$" }++ ],++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."++ },++ "cursor": {++ "type": "string",++ "maxLength": 512,++ "description": "next_cursor of the previous page."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-list-result.v1.json",++ "title": "RunJobListResult",++ "description": "run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "jobs",++ "next_cursor"++ ],++ "properties": {++ "jobs": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "$ref": "job-read-result.v1.json"++ }++ },++ "next_cursor": {++ "type": ["string", "null"],++ "maxLength": 512,++ "description": "Pass as cursor for the next page; null on the last page."++ }++ }++}nnew file mode 100644@@@ -0,0 +1,211 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-read-result.v1.json",++ "title": "RunJobReadResult",++ "description": "run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "inputs",++ "limits",++ "ttl_ms",++ "egress",++ "requirements",++ "idempotency_key",++ "placement",++ "timings",++ "exit",++ "result",++ "usage",++ "error"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>)."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The project the caller's token names; usage is billed to it."++ },++ "requester": {++ "$ref": "../identity/subject-ref.v1.json",++ "description": "The token principal that submitted the job."++ },++ "state": {++ "enum": [++ "queued",++ "placed",++ "running",++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ]++ },++ "class": {++ "$ref": "../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ { "$ref": "job-create-request.v1.json#/$defs/artifact" },++ { "type": "null" }++ ]++ },++ "inputs": {++ "type": "array",++ "maxItems": 32,++ "items": { "$ref": "job-create-request.v1.json#/$defs/input" }++ },++ "limits": {++ "$ref": "job-create-request.v1.json#/$defs/limits"++ },++ "ttl_ms": {++ "type": "integer",++ "minimum": 1,++ "description": "The job's lifetime from created_at, the default filled in."++ },++ "egress": {++ "$ref": "job-create-request.v1.json#/$defs/egress"++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json",++ "description": "What the job was placed against, the default filled in."++ },++ "idempotency_key": {++ "anyOf": [++ { "$ref": "job-create-request.v1.json#/$defs/idempotency_key" },++ { "type": "null" }++ ]++ },++ "placement": {++ "anyOf": [++ { "$ref": "#/$defs/placement" },++ { "type": "null" }++ ],++ "description": "Where the job was sent; null while queued, and for a job cancelled or expired before placement."++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": ["created_at", "placed_at", "started_at", "finished_at"],++ "properties": {++ "created_at": { "type": "string", "format": "date-time" },++ "placed_at": { "type": ["string", "null"], "format": "date-time" },++ "started_at": { "type": ["string", "null"], "format": "date-time", "description": "From job_started started_ms; null until the process started." },++ "finished_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job reached its end state; null before." }++ }++ },++ "exit": {++ "anyOf": [++ { "$ref": "#/$defs/exit" },++ { "type": "null" }++ ],++ "description": "The node's job_exit; null until it arrived, and for a job that ended before a node ran it."++ },++ "result": {++ "description": "The artifact's declared output (job_exit result, any JSON); null unless state is succeeded."++ },++ "usage": {++ "$ref": "#/$defs/usage"++ },++ "error": {++ "anyOf": [++ { "$ref": "#/$defs/error" },++ { "type": "null" }++ ],++ "description": "Why the job failed or expired; null in every other state."++ }++ },++ "$defs": {++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": ["node", "provider", "region"],++ "properties": {++ "node": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The worker's device id (the usage reading's `node`)." },++ "provider": { "type": ["string", "null"], "maxLength": 128, "description": "The offer's provider (the usage reading's `provider`); null for a first-party node." },++ "region": { "type": ["string", "null"], "maxLength": 64 }++ }++ },++ "exit": {++ "type": "object",++ "additionalProperties": false,++ "required": ["reason", "code"],++ "properties": {++ "reason": {++ "enum": ["exited", "cancelled", "ttl", "limit", "stopped", "failed"],++ "description": "platform.job-frame@1 job_exit reason."++ },++ "code": { "type": ["integer", "null"], "description": "The process's exit status; null when it was killed or never ran." }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": ["seconds", "wall_ms", "cpu_ms", "mem_peak_bytes"],++ "properties": {++ "seconds": { "type": "number", "minimum": 0, "description": "Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started." },++ "wall_ms": { "type": "integer", "minimum": 0 },++ "cpu_ms": { "type": ["integer", "null"], "minimum": 0, "description": "Informational, never billed." },++ "mem_peak_bytes": { "type": ["integer", "null"], "minimum": 0, "description": "Informational, never billed." }++ }++ },++ "error": {++ "type": "object",++ "additionalProperties": false,++ "required": ["code", "detail"],++ "properties": {++ "code": {++ "type": "string",++ "pattern": "^run\\.[a-z0-9_]+(\\.[a-z0-9_]+)*$",++ "description": "run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl."++ },++ "detail": { "type": ["string", "null"], "maxLength": 1024 }++ }++ }++ },++ "allOf": [++ {++ "if": { "properties": { "state": { "const": "queued" } }, "required": ["state"] },++ "then": { "properties": { "placement": { "type": "null" }, "exit": { "type": "null" }, "timings": { "type": "object", "properties": { "placed_at": { "type": "null" }, "started_at": { "type": "null" }, "finished_at": { "type": "null" } } } } }++ },++ {++ "if": { "properties": { "state": { "enum": ["placed", "running"] } }, "required": ["state"] },++ "then": { "properties": { "placement": { "type": "object" }, "exit": { "type": "null" }, "timings": { "type": "object", "properties": { "placed_at": { "type": "string" }, "finished_at": { "type": "null" } } } } }++ },++ {++ "if": { "properties": { "state": { "const": "running" } }, "required": ["state"] },++ "then": { "properties": { "timings": { "type": "object", "properties": { "started_at": { "type": "string" } } } } }++ },++ {++ "if": { "properties": { "state": { "enum": ["succeeded", "failed", "cancelled", "expired"] } }, "required": ["state"] },++ "then": { "properties": { "timings": { "type": "object", "properties": { "finished_at": { "type": "string" } } } } }++ },++ {++ "if": { "properties": { "state": { "const": "succeeded" } }, "required": ["state"] },++ "then": { "properties": { "placement": { "type": "object" }, "exit": { "type": "object", "properties": { "reason": { "const": "exited" }, "code": { "const": 0 } } }, "timings": { "type": "object", "properties": { "started_at": { "type": "string" } } } } }++ },++ {++ "if": { "properties": { "state": { "enum": ["failed", "expired"] } }, "required": ["state"] },++ "then": { "properties": { "error": { "type": "object" } } },++ "else": { "properties": { "error": { "type": "null" } } }++ },++ {++ "if": { "properties": { "state": { "const": "succeeded" } }, "required": ["state"] },++ "else": { "properties": { "result": { "type": "null" } } }++ },++ {++ "if": { "properties": { "class": { "enum": ["function", "code"] } }, "required": ["class"] },++ "then": { "properties": { "artifact": { "type": "object" } } }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,55 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-stream-event.v1.json",++ "title": "RunJobStreamEvent",++ "description": "run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "type",++ "job_id",++ "seq",++ "at"++ ],++ "properties": {++ "type": {++ "enum": ["output", "state", "end"]++ },++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "seq": {++ "type": "integer",++ "minimum": 1,++ "description": "The job's own stream counter from 1; the SSE id."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ },++ "chunk": {++ "type": "string",++ "maxLength": 65536,++ "description": "output only: UTF-8 text."++ },++ "state": {++ "enum": ["queued", "placed", "running", "succeeded", "failed", "cancelled", "expired"],++ "description": "state and end: the job's state."++ }++ },++ "allOf": [++ {++ "if": { "properties": { "type": { "const": "output" } }, "required": ["type"] },++ "then": { "required": ["chunk"], "not": { "required": ["state"] } }++ },++ {++ "if": { "properties": { "type": { "enum": ["state", "end"] } }, "required": ["type"] },++ "then": { "required": ["state"], "not": { "required": ["chunk"] } }++ },++ {++ "if": { "properties": { "type": { "const": "end" } }, "required": ["type"] },++ "then": { "properties": { "state": { "enum": ["succeeded", "failed", "cancelled", "expired"] } } }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1,42 @@++{++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-stream-ticket-result.v1.json",++ "title": "RunJobStreamTicketResult",++ "description": "run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ticket",++ "expires_at",++ "expires_in",++ "stream_url",++ "job_id"++ ],++ "properties": {++ "ticket": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",++ "maxLength": 4096,++ "description": "The signed ticket (a compact JWS)."++ },++ "expires_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_in": {++ "type": "integer",++ "minimum": 1,++ "maximum": 300,++ "description": "Seconds until the ticket expires (120)."++ },++ "stream_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://[^?#]+/api/v1/jobs/job_[0-9A-HJKMNP-TV-Z]{26}/stream$"++ },++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++}nnew file mode 100644@@@ -0,0 +1,8 @@++{++ "class": "function",++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ }++}nnew file mode 100644@@@ -0,0 +1,27 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113,++ "url": "https://example.com/photo.jpg"++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,24 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS"++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,25 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000++ },++ "ttl_ms": 630000,++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "lan",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "^1.2"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "class": "code",++ "artifact": {++ "name": "py-runner",++ "version": "3.12.1"++ },++ "args": {++ "entry": "main.py"++ },++ "limits": {++ "wall_ms": 600000,++ "cpu_ms": 600000,++ "mem_bytes": 1073741824++ },++ "egress": "public",++ "requirements": {++ "kind": "run.code",++ "mobility": "job",++ "latency_class": "background",++ "objective": "cheapest",++ "trust": [++ "first-party",++ "user-owned"++ ]++ }++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++}nnew file mode 100644@@@ -0,0 +1,54 @@++{++ "job": {++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "queued",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++ }++}nnew file mode 100644@@@ -0,0 +1,55 @@++{++ "created": true,++ "job": {++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "queued",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++ }++}nnew file mode 100644@@@ -0,0 +1,59 @@++{++ "created": false,++ "job": {++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "running",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++ }++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "limit": "201"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "state": "done"++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "limit": 200++}nnew file mode 100644@@@ -0,0 +1,5 @@++{++ "state": "running",++ "limit": "50",++ "cursor": "eyJpZCI6ImpvYl8wMSJ9"++}nnew file mode 100644@@@ -0,0 +1,3 @@++{++ "jobs": []++}nnew file mode 100644@@@ -0,0 +1,4 @@++{++ "jobs": [],++ "next_cursor": null++}nnew file mode 100644@@@ -0,0 +1,118 @@++{++ "jobs": [++ {++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "succeeded",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "exited",++ "code": 0++ },++ "result": {++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++ },++ {++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "queued",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++ }++ ],++ "next_cursor": "eyJpZCI6ImpvYl8wMSJ9"++}nnew file mode 100644@@@ -0,0 +1,59 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "failed",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "limit",++ "code": null++ },++ "result": null,++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,54 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "queued",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": {++ "ok": true++ },++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "running",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,61 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "succeeded",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "exited",++ "code": 1++ },++ "result": {++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "paused",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,55 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "expired",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": "2026-10-04T10:10:30.000Z"++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": {++ "code": "run.job.unplaceable",++ "detail": "no node offers worker:function with egress none"++ }++}nnew file mode 100644@@@ -0,0 +1,62 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "failed",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "limit",++ "code": null++ },++ "result": null,++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": {++ "code": "run.job.limit",++ "detail": "mem_bytes exceeded"++ }++}nnew file mode 100644@@@ -0,0 +1,52 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "queued",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,56 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "running",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": null++ },++ "exit": null,++ "result": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,61 @@++{++ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "ttl_ms": 630000,++ "egress": "none",++ "requirements": {++ "kind": "run.function",++ "mobility": "job",++ "latency_class": "background",++ "objective": "balanced"++ },++ "idempotency_key": "thumb:photo-7",++ "state": "succeeded",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "exited",++ "code": 0++ },++ "result": {++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "type": "end",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "seq": 4,++ "at": "2026-10-04T10:00:03.000Z",++ "state": "running"++}nnew file mode 100644@@@ -0,0 +1,6 @@++{++ "type": "output",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "seq": 3,++ "at": "2026-10-04T10:00:01.000Z"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "type": "end",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "seq": 4,++ "at": "2026-10-04T10:00:03.000Z",++ "state": "succeeded"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "type": "output",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "seq": 3,++ "at": "2026-10-04T10:00:01.000Z",++ "chunk": "resized 1 of 1\n"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "type": "state",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "seq": 2,++ "at": "2026-10-04T10:00:00.650Z",++ "state": "running"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "ticket": "sample-ticket.not-a-jwt.placeholder",++ "expires_at": "2026-10-04T10:02:00.000Z",++ "expires_in": 3600,++ "stream_url": "https://openvibe.run/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/stream",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "ticket": "sample-ticket.not-a-jwt.placeholder",++ "expires_at": "2026-10-04T10:02:00.000Z",++ "expires_in": 120,++ "stream_url": "https://openvibe.run/api/v1/jobs",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"++}nnew file mode 100644@@@ -0,0 +1,7 @@++{++ "ticket": "sample-ticket.not-a-jwt.placeholder",++ "expires_at": "2026-10-04T10:02:00.000Z",++ "expires_in": 120,++ "stream_url": "https://openvibe.run/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/stream",++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "cancelled",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "cancelled",++ "code": null++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": {++ "code": "run.job.limit",++ "detail": null++ }++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "cancelled",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": "2026-10-04T10:00:05.000Z"++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "cancelled",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "cancelled",++ "code": null++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "expired",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": {++ "code": "run.job.unplaceable",++ "detail": "no node offers worker:function with egress none"++ }++}nnew file mode 100644@@@ -0,0 +1,33 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "expired",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": "2026-10-04T10:10:30.000Z"++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": {++ "code": "run.job.unplaceable",++ "detail": "no node offers worker:function with egress none"++ }++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "failed",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "limit",++ "code": null++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,40 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "failed",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "limit",++ "code": null++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": {++ "code": "run.job.limit",++ "detail": "mem_bytes exceeded"++ }++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "queued",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "queued",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": null,++ "started_at": null,++ "finished_at": null++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,30 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "running",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": null,++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": null++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,34 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "running",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": null++ },++ "exit": null,++ "usage": {++ "seconds": 0,++ "wall_ms": 0,++ "cpu_ms": null,++ "mem_peak_bytes": null++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "succeeded",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "exited",++ "code": 2++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}nnew file mode 100644@@@ -0,0 +1,37 @@++{++ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",++ "requester": {++ "type": "app",++ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"++ },++ "state": "succeeded",++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "egress": "none",++ "placement": {++ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",++ "provider": null,++ "region": "us-west"++ },++ "timings": {++ "created_at": "2026-10-04T10:00:00.000Z",++ "placed_at": "2026-10-04T10:00:00.400Z",++ "started_at": "2026-10-04T10:00:00.650Z",++ "finished_at": "2026-10-04T10:00:03.000Z"++ },++ "exit": {++ "reason": "exited",++ "code": 0++ },++ "usage": {++ "seconds": 2.35,++ "wall_ms": 2350,++ "cpu_ms": 1800,++ "mem_peak_bytes": 41943040++ },++ "error": null++}@@@ -63363,6 +63363,1701 @@}]}++ },++ "run.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/run/job-create-request.v1.json",++ "title": "RunJobCreateRequest",++ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "class",++ "limits"++ ],++ "properties": {++ "class": {++ "$ref": "../platform/runtime-class.v1.json"++ },++ "artifact": {++ "$ref": "#/$defs/artifact"++ },++ "args": {++ "type": "object",++ "description": "The artifact's declared input as JSON; default {}."++ },++ "inputs": {++ "type": "array",++ "maxItems": 32,++ "items": {++ "$ref": "#/$defs/input"++ },++ "description": "Files placed in the job directory before the process starts; names are unique (else 422)."++ },++ "limits": {++ "$ref": "#/$defs/limits"++ },++ "ttl_ms": {++ "type": "integer",++ "minimum": 1000,++ "maximum": 86400000,++ "description": "Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000."++ },++ "egress": {++ "$ref": "#/$defs/egress"++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json"++ },++ "idempotency_key": {++ "$ref": "#/$defs/idempotency_key"++ }++ },++ "allOf": [++ {++ "if": {++ "required": [++ "class"++ ],++ "properties": {++ "class": {++ "enum": [++ "function",++ "code"++ ]++ }++ }++ },++ "then": {++ "required": [++ "artifact"++ ]++ }++ }++ ],++ "$defs": {++ "artifact": {++ "type": "object",++ "additionalProperties": false,++ "description": "The pre-registered artifact to run (platform.job@1 artifact).",++ "required": [++ "name",++ "version"++ ],++ "properties": {++ "name": {++ "type": "string",++ "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$"++ },++ "version": {++ "type": "string",++ "pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$",++ "description": "One exact version, never a range"++ }++ }++ },++ "input": {++ "type": "object",++ "additionalProperties": false,++ "description": "One input file: an OpenVibe.Media object the caller's project can read, pinned by digest.",++ "required": [++ "name",++ "media_id",++ "sha256"++ ],++ "properties": {++ "name": {++ "type": "string",++ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$",++ "description": "File name in the job directory; no path separators."++ },++ "media_id": {++ "type": "string",++ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "sha256": {++ "type": "string",++ "pattern": "^[0-9a-f]{64}$",++ "description": "Lowercase hex SHA-256 of the object's bytes."++ },++ "size_bytes": {++ "type": "integer",++ "minimum": 0++ }++ }++ },++ "limits": {++ "type": "object",++ "additionalProperties": false,++ "description": "Caps on the running process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.",++ "required": [++ "wall_ms",++ "cpu_ms",++ "mem_bytes"++ ],++ "properties": {++ "wall_ms": {++ "type": "integer",++ "minimum": 1,++ "description": "Running time from `job_started`"++ },++ "cpu_ms": {++ "type": "integer",++ "minimum": 1,++ "description": "CPU time, user plus system"++ },++ "mem_bytes": {++ "type": "integer",++ "minimum": 1,++ "description": "Peak resident memory"++ }++ }++ },++ "egress": {++ "type": "string",++ "enum": [++ "none",++ "public",++ "openvibe-only"++ ],++ "default": "none",++ "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."++ },++ "idempotency_key": {++ "type": "string",++ "pattern": "^[A-Za-z0-9._:-]{1,128}$"++ }++ },++ "examples": [++ {++ "class": "function",++ "artifact": {++ "name": "thumbnail",++ "version": "1.2.0"++ },++ "args": {++ "width": 320++ },++ "inputs": [++ {++ "name": "photo.jpg",++ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",++ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",++ "size_bytes": 482113++ }++ ],++ "limits": {++ "wall_ms": 30000,++ "cpu_ms": 30000,++ "mem_bytes": 268435456++ },++ "egress": "none",++ "idempotency_key": "thumb:photo-7"++ }++ ]++ }++ },++ "run.job-create-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-create-result.v1.json",++ "title": "RunJobCreateResult",++ "description": "run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created",++ "job"++ ],++ "properties": {++ "created": {++ "type": "boolean",++ "description": "false when the idempotency_key matched an earlier job."++ },++ "job": {++ "$ref": "job-read-result.v1.json"++ }++ }++ }++ },++ "run.job-read-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-read-result.v1.json",++ "title": "RunJobReadResult",++ "description": "run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "inputs",++ "limits",++ "ttl_ms",++ "egress",++ "requirements",++ "idempotency_key",++ "placement",++ "timings",++ "exit",++ "result",++ "usage",++ "error"++ ],++ "properties": {++ "id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>)."++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "The project the caller's token names; usage is billed to it."++ },++ "requester": {++ "$ref": "../identity/subject-ref.v1.json",++ "description": "The token principal that submitted the job."++ },++ "state": {++ "enum": [++ "queued",++ "placed",++ "running",++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ]++ },++ "class": {++ "$ref": "../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "inputs": {++ "type": "array",++ "maxItems": 32,++ "items": {++ "$ref": "job-create-request.v1.json#/$defs/input"++ }++ },++ "limits": {++ "$ref": "job-create-request.v1.json#/$defs/limits"++ },++ "ttl_ms": {++ "type": "integer",++ "minimum": 1,++ "description": "The job's lifetime from created_at, the default filled in."++ },++ "egress": {++ "$ref": "job-create-request.v1.json#/$defs/egress"++ },++ "requirements": {++ "$ref": "../platform/workload-requirements.v1.json",++ "description": "What the job was placed against, the default filled in."++ },++ "idempotency_key": {++ "anyOf": [++ {++ "$ref": "job-create-request.v1.json#/$defs/idempotency_key"++ },++ {++ "type": "null"++ }++ ]++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "#/$defs/placement"++ },++ {++ "type": "null"++ }++ ],++ "description": "Where the job was sent; null while queued, and for a job cancelled or expired before placement."++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "From job_started started_ms; null until the process started."++ },++ "finished_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time",++ "description": "When the job reached its end state; null before."++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "#/$defs/exit"++ },++ {++ "type": "null"++ }++ ],++ "description": "The node's job_exit; null until it arrived, and for a job that ended before a node ran it."++ },++ "result": {++ "description": "The artifact's declared output (job_exit result, any JSON); null unless state is succeeded."++ },++ "usage": {++ "$ref": "#/$defs/usage"++ },++ "error": {++ "anyOf": [++ {++ "$ref": "#/$defs/error"++ },++ {++ "type": "null"++ }++ ],++ "description": "Why the job failed or expired; null in every other state."++ }++ },++ "$defs": {++ "placement": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "node",++ "provider",++ "region"++ ],++ "properties": {++ "node": {++ "type": "string",++ "minLength": 1,++ "maxLength": 128,++ "description": "The worker's device id (the usage reading's `node`)."++ },++ "provider": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 128,++ "description": "The offer's provider (the usage reading's `provider`); null for a first-party node."++ },++ "region": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 64++ }++ }++ },++ "exit": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "reason",++ "code"++ ],++ "properties": {++ "reason": {++ "enum": [++ "exited",++ "cancelled",++ "ttl",++ "limit",++ "stopped",++ "failed"++ ],++ "description": "platform.job-frame@1 job_exit reason."++ },++ "code": {++ "type": [++ "integer",++ "null"++ ],++ "description": "The process's exit status; null when it was killed or never ran."++ }++ }++ },++ "usage": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "seconds",++ "wall_ms",++ "cpu_ms",++ "mem_peak_bytes"++ ],++ "properties": {++ "seconds": {++ "type": "number",++ "minimum": 0,++ "description": "Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started."++ },++ "wall_ms": {++ "type": "integer",++ "minimum": 0++ },++ "cpu_ms": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 0,++ "description": "Informational, never billed."++ },++ "mem_peak_bytes": {++ "type": [++ "integer",++ "null"++ ],++ "minimum": 0,++ "description": "Informational, never billed."++ }++ }++ },++ "error": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "code",++ "detail"++ ],++ "properties": {++ "code": {++ "type": "string",++ "pattern": "^run\\.[a-z0-9_]+(\\.[a-z0-9_]+)*$",++ "description": "run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl."++ },++ "detail": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 1024++ }++ }++ }++ },++ "allOf": [++ {++ "if": {++ "properties": {++ "state": {++ "const": "queued"++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "placement": {++ "type": "null"++ },++ "exit": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "placed_at": {++ "type": "null"++ },++ "started_at": {++ "type": "null"++ },++ "finished_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "enum": [++ "placed",++ "running"++ ]++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ },++ "exit": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "properties": {++ "placed_at": {++ "type": "string"++ },++ "finished_at": {++ "type": "null"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "const": "running"++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "timings": {++ "type": "object",++ "properties": {++ "started_at": {++ "type": "string"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "enum": [++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ]++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "timings": {++ "type": "object",++ "properties": {++ "finished_at": {++ "type": "string"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "const": "succeeded"++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "placement": {++ "type": "object"++ },++ "exit": {++ "type": "object",++ "properties": {++ "reason": {++ "const": "exited"++ },++ "code": {++ "const": 0++ }++ }++ },++ "timings": {++ "type": "object",++ "properties": {++ "started_at": {++ "type": "string"++ }++ }++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "enum": [++ "failed",++ "expired"++ ]++ }++ },++ "required": [++ "state"++ ]++ },++ "then": {++ "properties": {++ "error": {++ "type": "object"++ }++ }++ },++ "else": {++ "properties": {++ "error": {++ "type": "null"++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "state": {++ "const": "succeeded"++ }++ },++ "required": [++ "state"++ ]++ },++ "else": {++ "properties": {++ "result": {++ "type": "null"++ }++ }++ }++ },++ {++ "if": {++ "properties": {++ "class": {++ "enum": [++ "function",++ "code"++ ]++ }++ },++ "required": [++ "class"++ ]++ },++ "then": {++ "properties": {++ "artifact": {++ "type": "object"++ }++ }++ }++ }++ ]++ }++ },++ "run.job-list-query": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-list-query.v1.json",++ "title": "RunJobListQuery",++ "description": "run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.",++ "type": "object",++ "additionalProperties": false,++ "properties": {++ "state": {++ "enum": [++ "queued",++ "placed",++ "running",++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ]++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",++ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."++ },++ "limit": {++ "oneOf": [++ {++ "type": "integer",++ "minimum": 1,++ "maximum": 200++ },++ {++ "type": "string",++ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"++ }++ ],++ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."++ },++ "cursor": {++ "type": "string",++ "maxLength": 512,++ "description": "next_cursor of the previous page."++ }++ }++ }++ },++ "run.job-list-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-list-result.v1.json",++ "title": "RunJobListResult",++ "description": "run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "jobs",++ "next_cursor"++ ],++ "properties": {++ "jobs": {++ "type": "array",++ "maxItems": 200,++ "items": {++ "$ref": "job-read-result.v1.json"++ }++ },++ "next_cursor": {++ "type": [++ "string",++ "null"++ ],++ "maxLength": 512,++ "description": "Pass as cursor for the next page; null on the last page."++ }++ }++ }++ },++ "run.job-stream-ticket-result": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-stream-ticket-result.v1.json",++ "title": "RunJobStreamTicketResult",++ "description": "run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "ticket",++ "expires_at",++ "expires_in",++ "stream_url",++ "job_id"++ ],++ "properties": {++ "ticket": {++ "type": "string",++ "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",++ "maxLength": 4096,++ "description": "The signed ticket (a compact JWS)."++ },++ "expires_at": {++ "type": "string",++ "format": "date-time"++ },++ "expires_in": {++ "type": "integer",++ "minimum": 1,++ "maximum": 300,++ "description": "Seconds until the ticket expires (120)."++ },++ "stream_url": {++ "type": "string",++ "format": "uri",++ "pattern": "^https?://[^?#]+/api/v1/jobs/job_[0-9A-HJKMNP-TV-Z]{26}/stream$"++ },++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ }++ }++ }++ },++ "run.job-stream-event": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/run/job-stream-event.v1.json",++ "title": "RunJobStreamEvent",++ "description": "run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.",++ "type": "object",++ "additionalProperties": false,++ "required": [++ "type",++ "job_id",++ "seq",++ "at"++ ],++ "properties": {++ "type": {++ "enum": [++ "output",++ "state",++ "end"++ ]++ },++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "seq": {++ "type": "integer",++ "minimum": 1,++ "description": "The job's own stream counter from 1; the SSE id."++ },++ "at": {++ "type": "string",++ "format": "date-time"++ },++ "chunk": {++ "type": "string",++ "maxLength": 65536,++ "description": "output only: UTF-8 text."++ },++ "state": {++ "enum": [++ "queued",++ "placed",++ "running",++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ],++ "description": "state and end: the job's state."++ }++ },++ "allOf": [++ {++ "if": {++ "properties": {++ "type": {++ "const": "output"++ }++ },++ "required": [++ "type"++ ]++ },++ "then": {++ "required": [++ "chunk"++ ],++ "not": {++ "required": [++ "state"++ ]++ }++ }++ },++ {++ "if": {++ "properties": {++ "type": {++ "enum": [++ "state",++ "end"++ ]++ }++ },++ "required": [++ "type"++ ]++ },++ "then": {++ "required": [++ "state"++ ],++ "not": {++ "required": [++ "chunk"++ ]++ }++ }++ },++ {++ "if": {++ "properties": {++ "type": {++ "const": "end"++ }++ },++ "required": [++ "type"++ ]++ },++ "then": {++ "properties": {++ "state": {++ "enum": [++ "succeeded",++ "failed",++ "cancelled",++ "expired"++ ]++ }++ }++ }++ }++ ]++ }++ },++ "run.job.queued": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.queued.v1.json",++ "title": "RunJobQueuedPayload",++ "description": "run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "queued",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "type": "null"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "null"++ },++ "started_at": {++ "type": "null"++ },++ "finished_at": {++ "type": "null"++ }++ }++ },++ "exit": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++ }++ },++ "run.job.started": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.started.v1.json",++ "title": "RunJobStartedPayload",++ "description": "run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "running",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": "null"++ }++ }++ },++ "exit": {++ "type": "null"++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++ }++ },++ "run.job.succeeded": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.succeeded.v1.json",++ "title": "RunJobSucceededPayload",++ "description": "run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "succeeded",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": "string",++ "format": "date-time"++ },++ "started_at": {++ "type": "string",++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "allOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "object",++ "properties": {++ "reason": {++ "const": "exited"++ },++ "code": {++ "const": 0++ }++ }++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++ }++ },++ "run.job.failed": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.failed.v1.json",++ "title": "RunJobFailedPayload",++ "description": "run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "failed",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/error"++ }++ }++ }++ },++ "run.job.cancelled": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.cancelled.v1.json",++ "title": "RunJobCancelledPayload",++ "description": "run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "cancelled",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "type": "null"++ }++ }++ }++ },++ "run.job.expired": {++ "version": "1.0.0",++ "owner": "run",++ "status": "planned",++ "schema": {++ "$schema": "https://json-schema.org/draft/2020-12/schema",++ "$id": "https://openvibe.network/contracts/events/payloads/run.job.expired.v1.json",++ "title": "RunJobExpiredPayload",++ "description": "run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",++ "type": "object",++ "required": [++ "job_id",++ "project_id",++ "requester",++ "state",++ "class",++ "artifact",++ "egress",++ "placement",++ "timings",++ "exit",++ "usage",++ "error"++ ],++ "additionalProperties": false,++ "properties": {++ "job_id": {++ "type": "string",++ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "project_id": {++ "type": "string",++ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"++ },++ "requester": {++ "$ref": "../../identity/subject-ref.v1.json"++ },++ "state": {++ "const": "expired",++ "description": "The job state (run.job-read-result@1 state)."++ },++ "class": {++ "$ref": "../../platform/runtime-class.v1.json"++ },++ "artifact": {++ "anyOf": [++ {++ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"++ },++ {++ "type": "null"++ }++ ]++ },++ "egress": {++ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"++ },++ "placement": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"++ },++ {++ "type": "null"++ }++ ]++ },++ "timings": {++ "type": "object",++ "additionalProperties": false,++ "required": [++ "created_at",++ "placed_at",++ "started_at",++ "finished_at"++ ],++ "properties": {++ "created_at": {++ "type": "string",++ "format": "date-time"++ },++ "placed_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "started_at": {++ "type": [++ "string",++ "null"++ ],++ "format": "date-time"++ },++ "finished_at": {++ "type": "string",++ "format": "date-time"++ }++ }++ },++ "exit": {++ "anyOf": [++ {++ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"++ },++ {++ "type": "null"++ }++ ]++ },++ "usage": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"++ },++ "error": {++ "$ref": "../../run/job-read-result.v1.json#/$defs/error"++ }++ }++ }}}}@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.AI API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Billing API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Blog API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Bot API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Chat API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Codes API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Community API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Coupons API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Deals API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Events API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Games API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Host API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Live API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Media API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Network API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.News API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenRe.Stream API",-- "version": "0.89.0",-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Reviews API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Search API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Sources API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Space API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tips API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Tools API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Trade API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.VIP API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -3,8 +3,8 @@"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema","info": {"title": "OpenVibe.Wiki API",-- "version": "0.89.0",-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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"@@@ -52408,3 +52408,700 @@ export type BotDeviceConnectResult =| {device: BotDevice;};++++/** [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. 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: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). 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.++ */++export type RunJobCreateRequest = {++ [k: string]: unknown | undefined;++} & {++ class: RuntimeClass;++ /**++ * The pre-registered artifact to run (platform.job@1 artifact).++ */++ artifact?: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ };++ /**++ * The artifact's declared input as JSON; default {}.++ */++ args?: {};++ /**++ * Files placed in the job directory before the process starts; names are unique (else 422).++ *++ * @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 process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.++ */++ limits: {++ /**++ * Running time from `job_started`++ */++ wall_ms: number;++ /**++ * CPU time, user plus system++ */++ cpu_ms: number;++ /**++ * Peak resident memory++ */++ mem_bytes: number;++ };++ /**++ * Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000.++ */++ ttl_ms?: 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";++ requirements?: WorkloadRequirements;++ idempotency_key?: string;++};++++/** [email protected] (owner: run) */++/**++ * run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).++ */++export interface RunJobCreateResult {++ /**++ * false when the idempotency_key matched an earlier job.++ */++ created: boolean;++ job: RunJobReadResult;++}++++/** [email protected] (owner: run) */++/**++ * run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.++ */++export type RunJobReadResult = {++ [k: string]: unknown | undefined;++} & {++ /**++ * Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>).++ */++ id: string;++ /**++ * The project the caller's token names; usage is billed to it.++ */++ project_id: string;++ requester: SubjectRef;++ state: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * @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 process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.++ */++ limits: {++ /**++ * Running time from `job_started`++ */++ wall_ms: number;++ /**++ * CPU time, user plus system++ */++ cpu_ms: number;++ /**++ * Peak resident memory++ */++ mem_bytes: number;++ };++ /**++ * The job's lifetime from created_at, the default filled in.++ */++ ttl_ms: 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";++ requirements: WorkloadRequirements;++ idempotency_key: string | null;++ /**++ * Where the job was sent; null while queued, and for a job cancelled or expired before placement.++ */++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ } | null;++ timings: {++ created_at: string;++ placed_at: string | null;++ /**++ * From job_started started_ms; null until the process started.++ */++ started_at: string | null;++ /**++ * When the job reached its end state; null before.++ */++ finished_at: string | null;++ };++ /**++ * The node's job_exit; null until it arrived, and for a job that ended before a node ran it.++ */++ exit: {++ /**++ * platform.job-frame@1 job_exit reason.++ */++ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";++ /**++ * The process's exit status; null when it was killed or never ran.++ */++ code: number | null;++ } | null;++ /**++ * The artifact's declared output (job_exit result, any JSON); null unless state is succeeded.++ */++ result: {++ [k: string]: unknown | undefined;++ };++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ /**++ * Why the job failed or expired; null in every other state.++ */++ error: {++ /**++ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.++ */++ code: string;++ detail: string | null;++ } | null;++};++++/** [email protected] (owner: run) */++/**++ * run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.++ */++export interface RunJobListQuery {++ state?: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";++ /**++ * Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403.++ */++ project_id?: string;++ /**++ * Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range.++ */++ limit?: number | string;++ /**++ * next_cursor of the previous page.++ */++ cursor?: string;++}++++/** [email protected] (owner: run) */++/**++ * run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.++ */++export interface RunJobListResult {++ /**++ * @maxItems 200++ */++ jobs: RunJobReadResult[];++ /**++ * Pass as cursor for the next page; null on the last page.++ */++ next_cursor: string | null;++}++++/** [email protected] (owner: run) */++/**++ * run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.++ */++export interface RunJobStreamTicketResult {++ /**++ * The signed ticket (a compact JWS).++ */++ ticket: string;++ expires_at: string;++ /**++ * Seconds until the ticket expires (120).++ */++ expires_in: number;++ stream_url: string;++ job_id: string;++}++++/** [email protected] (owner: run) */++/**++ * run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.++ */++export type RunJobStreamEvent = {++ [k: string]: unknown | undefined;++} & {++ type: "output" | "state" | "end";++ job_id: string;++ /**++ * The job's own stream counter from 1; the SSE id.++ */++ seq: number;++ at: string;++ /**++ * output only: UTF-8 text.++ */++ chunk?: string;++ /**++ * state and end: the job's state.++ */++ state?: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";++};++++/** [email protected] (owner: run) */++/**++ * run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.++ */++export interface RunJobQueuedPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "queued";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: null;++ timings: {++ created_at: string;++ placed_at: null;++ started_at: null;++ finished_at: null;++ };++ exit: null;++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: null;++}++++/** [email protected] (owner: run) */++/**++ * run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.++ */++export interface RunJobStartedPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "running";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ };++ timings: {++ created_at: string;++ placed_at: string;++ started_at: string;++ finished_at: null;++ };++ exit: null;++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: null;++}++++/** [email protected] (owner: run) */++/**++ * run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.++ */++export interface RunJobSucceededPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "succeeded";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ };++ timings: {++ created_at: string;++ placed_at: string;++ started_at: string;++ finished_at: string;++ };++ exit: {++ /**++ * platform.job-frame@1 job_exit reason.++ */++ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";++ /**++ * The process's exit status; null when it was killed or never ran.++ */++ code: number | null;++ } & {++ reason?: "exited";++ code?: 0;++ };++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: null;++}++++/** [email protected] (owner: run) */++/**++ * run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.++ */++export interface RunJobFailedPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "failed";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ } | null;++ timings: {++ created_at: string;++ placed_at: string | null;++ started_at: string | null;++ finished_at: string;++ };++ exit: {++ /**++ * platform.job-frame@1 job_exit reason.++ */++ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";++ /**++ * The process's exit status; null when it was killed or never ran.++ */++ code: number | null;++ } | null;++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: {++ /**++ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.++ */++ code: string;++ detail: string | null;++ };++}++++/** [email protected] (owner: run) */++/**++ * run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.++ */++export interface RunJobCancelledPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "cancelled";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ } | null;++ timings: {++ created_at: string;++ placed_at: string | null;++ started_at: string | null;++ finished_at: string;++ };++ exit: {++ /**++ * platform.job-frame@1 job_exit reason.++ */++ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";++ /**++ * The process's exit status; null when it was killed or never ran.++ */++ code: number | null;++ } | null;++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: null;++}++++/** [email protected] (owner: run) */++/**++ * run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.++ */++export interface RunJobExpiredPayload {++ job_id: string;++ project_id: string;++ requester: SubjectRef;++ /**++ * The job state (run.job-read-result@1 state).++ */++ state: "expired";++ class: RuntimeClass;++ artifact: {++ name: string;++ /**++ * One exact version, never a range++ */++ version: string;++ } | null;++ /**++ * 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";++ placement: {++ /**++ * The worker's device id (the usage reading's `node`).++ */++ node: string;++ /**++ * The offer's provider (the usage reading's `provider`); null for a first-party node.++ */++ provider: string | null;++ region: string | null;++ } | null;++ timings: {++ created_at: string;++ placed_at: string | null;++ started_at: string | null;++ finished_at: string;++ };++ exit: {++ /**++ * platform.job-frame@1 job_exit reason.++ */++ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";++ /**++ * The process's exit status; null when it was killed or never ran.++ */++ code: number | null;++ } | null;++ usage: {++ /**++ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.++ */++ seconds: number;++ wall_ms: number;++ /**++ * Informational, never billed.++ */++ cpu_ms: number | null;++ /**++ * Informational, never billed.++ */++ mem_peak_bytes: number | null;++ };++ error: {++ /**++ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.++ */++ code: string;++ detail: string | null;++ };++}@@@ -6,7 +6,7 @@const crypto = require('crypto');const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';--const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', watch: 'wch', observation: 'wco', check: 'ckr' };++const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', watch: 'wch', observation: 'wco', check: 'ckr' };const SLUG_TYPES = new Set(['service', 'system']);const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;nnew file mode 100644@@@ -0,0 +1,22 @@++{++ "id": "run.job.admin",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "internal",++ "sensitive": true,++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job. Never delegated and never in an app or mod token. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "run.job-list-query@1",++ "outputSchema": "run.job-list-result@1",++ "permissions": [++ "run:admin"++ ],++ "resourceConstraints": [++ "none"++ ],++ "quotaClass": "run-admin",++ "events": [++ "run.job.cancelled"++ ],++ "implementedBy": []++}nnew file mode 100644@@@ -0,0 +1,21 @@++{++ "id": "run.job.cancel",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "public",++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel. A queued or placed job never starts; a running one is killed (platform.job-frame@1 job_cancel); cancelling an ended job changes nothing and answers it as it is. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "common.no-body@1",++ "outputSchema": "run.job-read-result@1",++ "permissions": [++ "run:cancel"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "run-control",++ "events": [++ "run.job.cancelled"++ ],++ "implementedBy": []++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "run.job.list",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "public",++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "run.job-list-query@1",++ "outputSchema": "run.job-list-result@1",++ "permissions": [++ "run:read"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "run-read",++ "events": [],++ "implementedBy": []++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "run.job.read",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "public",++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result). It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "common.no-body@1",++ "outputSchema": "run.job-read-result@1",++ "permissions": [++ "run:read"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "run-read",++ "events": [],++ "implementedBy": []++}nnew file mode 100644@@@ -0,0 +1,19 @@++{++ "id": "run.job.stream",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "public",++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "common.no-body@1",++ "outputSchema": "run.job-stream-ticket-result@1",++ "permissions": [++ "run:stream"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "run-stream",++ "events": [],++ "implementedBy": []++}nnew file mode 100644@@@ -0,0 +1,26 @@++{++ "id": "run.job.submit",++ "version": "1.0",++ "owner": "run",++ "status": "planned",++ "visibility": "public",++ "sensitive": true,++ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Submit a job to OpenVibe.Run: POST /api/v1/jobs. The job belongs to the token's project, which is billed per wall-clock second it runs (service run, operation function.invoke, unit s). An idempotency_key makes a retry return the first job. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",++ "inputSchema": "run.job-create-request@1",++ "outputSchema": "run.job-create-result@1",++ "permissions": [++ "run:submit"++ ],++ "resourceConstraints": [++ "project"++ ],++ "quotaClass": "run-submit",++ "events": [++ "run.job.queued",++ "run.job.started",++ "run.job.succeeded",++ "run.job.failed",++ "run.job.expired"++ ],++ "implementedBy": []++}@@@ -27,6 +27,7 @@"keywords": "openvibe run, code sandbox, run code online, serverless functions, ai agent tasks","launch": "the sandbox runtime","relationships": {++ "service": "run","noRepo": true,"meanwhile": [{nnew file mode 100644@@@ -0,0 +1,58 @@++{++ "version": "0.0.0",++ "contractRanges": {++ "openvibe-contracts": ">=0.90.0 <1.0.0"++ },++ "id": "run",++ "name": "OpenVibe.Run",++ "status": "placeholder",++ "repository": "OpenVibers/OpenVibe.Run",++ "domains": [++ "openvibe.run"++ ],++ "publicOrigin": "https://openvibe.run",++ "exposure": {++ "state": "placeholder",++ "publicSite": null,++ "note": "planned (plan T14 R1); the OpenVibe.Run repository does not exist yet, and nothing serves openvibe.run"++ },++ "capabilities": [++ "run.job.submit",++ "run.job.read",++ "run.job.list",++ "run.job.cancel",++ "run.job.stream",++ "run.job.admin"++ ],++ "eventsProduced": [++ "run.job.queued",++ "run.job.started",++ "run.job.succeeded",++ "run.job.failed",++ "run.job.cancelled",++ "run.job.expired"++ ],++ "eventsConsumed": [],++ "namespacesOwned": [],++ "lifecycle": {++ "liveness": {++ "none": "not built yet; nothing listens on a loopback address"++ },++ "shutdown": {++ "none": "not built yet; nothing runs or drains"++ },++ "startupRecovery": {++ "none": "not built yet; nothing starts"++ },++ "rollback": {++ "none": "not built yet; nothing is released or rolled back"++ },++ "contracts": {++ "range": ">=0.90.0 <1.0.0"++ },++ "leases": {++ "none": "not built yet; nothing runs"++ }++ },++ "notes": "The job service of the network (plan T14, ADR-034): accepts jobs (run.job-create-request@1), places each on a node that advertises worker:<class> (platform.resource-offer@1, platform.runtime-offer@1, platform.workload-requirements@1), sends it as platform.job@1 over the node link and follows its platform.job-frame@1 frames. Consumes billing.usage.record to write one usage reading per wall-clock second (run:<job id>:<n>, service run, operation function.invoke, unit s) and media.object.read to resolve digest-pinned inputs. Planned: nothing is built."++}@@@ -1,12 +1,12 @@{"name": "openvibe-contracts",-- "version": "0.89.0",++ "version": "0.90.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-contracts",-- "version": "0.89.0",++ "version": "0.90.0","license": "AGPL-3.0-only","dependencies": {"ajv": "^8.17.1",@@@ -1,6 +1,6 @@{"name": "openvibe-contracts",-- "version": "0.89.0",++ "version": "0.90.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",@@@ -74,4 +74,25 @@ checks += 3;// job_usage only ever stands for a full second: it has no quantity of its own.ok(!contracts.validate('platform.job-frame', { ...usage, quantity: 0.5 }).valid, 'a job_usage frame cannot carry a partial quantity');++// OpenVibe.Run's job API (T14 R1): Run mints the platform.job@1 id, so its job ids, artifacts and limits are the++// job's own; a job's usage seconds are the sum of its readings; every run.* capability and event is Run's.++const create = contracts.schema('run.job-create-request');++const job = contracts.schema('platform.job');++ok(contracts.schema('run.job-read-result').properties.id.pattern === job.properties.id.pattern, 'a Run job id is a platform.job@1 id');++assert.deepStrictEqual(create.$defs.artifact.properties, job.properties.artifact.properties);++assert.deepStrictEqual(create.$defs.limits.properties, job.properties.limits.properties);++assert.deepStrictEqual(create.$defs.egress.enum, ['none', 'public', 'openvibe-only']);++checks += 3;++ok(contracts.ids.newId('job').match(job.properties.id.pattern), 'newId(job) mints a platform.job@1 id');++const succeeded = fixture('run.job-read-result', 'valid', 'succeeded');++ok(succeeded.usage.seconds === fromExit(exit).reduce((s, r) => s + r.quantity, 0) && succeeded.usage.wall_ms === exit.usage.wall_ms, 'usage.seconds is the sum of the job\'s readings');++const run = contracts.services.get('run');++ok(run && run.status === 'placeholder' && contracts.products.get('openvibe.run').relationships.noRepo === true, 'run is a placeholder service and openvibe.run still has no repository');++for (const id of ['run.job.submit', 'run.job.read', 'run.job.list', 'run.job.cancel', 'run.job.stream', 'run.job.admin']) {++ const c = contracts.capabilities.get(id);++ ok(c && c.owner === 'run' && c.status === 'planned' && c.description.startsWith('PLANNED') && run.capabilities.includes(id), `${id} is a planned capability Run serves`);++}++ok(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}`);++console.log(`platform run: ${checks} job, frame and metering checks passed`);@@@ -529,7 +529,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);--for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);++for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');
@@ -4,6 +4,32 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -4,6 +4,32 @@ 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,
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`).
the notes were in the tag and commit messages (`git tag -n1`).
+## 0.90.0 — 2026-10-04
+
+**OpenVibe.Run service and job API** (plan T14 R1, ADR-034). The contract OpenVibe.Run is built against; everything is
+planned (Run's repository does not exist yet) and additive.
+- New service manifest `manifests/services/run.json`: `run`, status and exposure `placeholder`, domain `openvibe.run`,
+ lifecycle all `none` (not built). `manifests/products/openvibe.run.json` gains `relationships.service: run` and keeps
+ `noRepo: true`.
+- New planned capabilities, owner `run`, each with an input and an output schema: `run.job.submit` (sensitive: it bills
+ the project), `run.job.read`, `run.job.list`, `run.job.cancel`, `run.job.stream` (public, project-scoped) and
+ `run.job.admin` (internal operator access to every project's jobs). `run.admin` is spelled `run.job.admin` because a
+ capability id has three segments.
+- New contracts under `contracts/run/`: `run.job-create-request@1` (class from `platform.runtime-class@1`, artifact
+ required for `function` and `code`, `inputs` as Media objects pinned by sha256, `limits` wall/cpu/mem as
+ `platform.job@1`, `ttl_ms`, `egress` `none | public | openvibe-only` as Node's `worker.egress`,
+ `requirements` as `platform.workload-requirements@1`, `idempotency_key`), `run.job-create-result@1`,
+ `run.job-read-result@1` (states `queued | placed | running | succeeded | failed | cancelled | expired`, placement node,
+ timings, exit, `usage.seconds`, result, error, with per-state rules), `run.job-list-query@1`, `run.job-list-result@1`
+ (`next_cursor` pages), `run.job-stream-ticket-result@1` (one-use, two-minute ticket, as Events' realtime ticket) and
+ `run.job-stream-event@1` (SSE `output | state | end`).
+- New planned events produced by Run: `run.job.queued`, `run.job.started`, `run.job.succeeded`, `run.job.failed`,
+ `run.job.cancelled` and `run.job.expired`, projections of the job without args, inputs, result or output.
+- `ids.newId('job')` mints `job_<ULID>`, the `platform.job@1` id Run passes to the node, so the metering key stays
+ `run:<job id>:<n>`.
+- Fixtures: valid and invalid for every new contract; `test/platform-run.test.js` pins the Run job id, artifact,
+ limits and egress to `platform.job@1` and Node, and `usage.seconds` to the sum of the job's readings.
+
## 0.89.0 — 2026-10-03
## 0.89.0 — 2026-10-03
**Agent tokens and confirmations** (plan T2 step 9; OpenVibe.Network `docs/t2-projects-and-grants.md` §2 gaps 1–3,
**Agent tokens and confirmations** (plan T2 step 9; OpenVibe.Network `docs/t2-projects-and-grants.md` §2 gaps 1–3,
@@ -216,6 +216,27 @@ Each service manifest has a `lifecycle` block, filled from the service's code an
@@ -216,6 +216,27 @@ Each service manifest has a `lifecycle` block, filled from the service's code an
`ovhost validate <service>` (OpenVibe.Host) reads the block from the inventory or from this package's manifest. It fails when a field is missing, when `deadlineSeconds` exceeds the unit's TimeoutStopSec or the unit's KillSignal is a different signal, and when the checkout's installed openvibe-contracts is outside `contracts.range`.
`ovhost validate <service>` (OpenVibe.Host) reads the block from the inventory or from this package's manifest. It fails when a field is missing, when `deadlineSeconds` exceeds the unit's TimeoutStopSec or the unit's KillSignal is a different signal, and when the checkout's installed openvibe-contracts is outside `contracts.range`.
+## Run job API (v0.90, planned)
+
+OpenVibe.Run (`manifests/services/run.json`, a placeholder until the repository exists; plan T14 R1) takes jobs,
+places each on a node that advertises `worker:<class>` and sends it as `platform.job@1`. Its routes, all planned:
+
+| Route | Capability | Body | Answer |
+| --- | --- | --- | --- |
+| `POST /api/v1/jobs` | `run.job.submit` | `run.job-create-request@1` | `run.job-create-result@1` (201, or 200 for an idempotent repeat) |
+| `GET /api/v1/jobs` | `run.job.list` | `run.job-list-query@1` (query string) | `run.job-list-result@1` (cursor pages) |
+| `GET /api/v1/jobs/:id` | `run.job.read` | — | `run.job-read-result@1` |
+| `POST /api/v1/jobs/:id/cancel` | `run.job.cancel` | — | `run.job-read-result@1` |
+| `POST /api/v1/jobs/:id/stream/ticket` | `run.job.stream` | — | `run.job-stream-ticket-result@1` (one-use, 120 s) |
+| `GET /api/v1/jobs/:id/stream?ticket=` | `run.job.stream` | — | SSE of `run.job-stream-event@1` (`output`, `state`, `end`; Last-Event-ID resumes) |
+| `GET /api/v1/admin/jobs` | `run.job.admin` (internal) | `run.job-list-query@1` with `project_id` | `run.job-list-result@1` |
+
+A job moves `queued → placed → running` and ends `succeeded`, `failed`, `cancelled` or `expired`; Run publishes
+`run.job.queued|started|succeeded|failed|cancelled|expired`. Run mints the job id (`job_<ULID>`, `ids.newId('job')`)
+and passes it unchanged as the `platform.job@1` id, so every metering reading is `run:<job id>:<n>` (service `run`,
+operation `function.invoke`, unit `s`) and `usage.seconds` is their sum. Inputs are Media objects pinned by sha256
+(never a URL); `egress` takes Node's `worker.egress` values (`none` by default, `public`, `openvibe-only`).
+
## Versioning and compatibility
## Versioning and compatibility
- A contract id is permanent. Minor versions only add optional fields.
- A contract id is permanent. Minor versions only add optional fields.
@@ -5694,6 +5694,136 @@
@@ -5694,6 +5694,136 @@
"visibility": "first-party",
"visibility": "first-party",
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"status": "active"
+ },
+ {
+ "id": "run.job-create-request",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-create-request.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-create-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-create-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-read-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-read-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-list-query",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-list-query.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-list-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-list-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-stream-ticket-result",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-stream-ticket-result.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job-stream-event",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "run/job-stream-event.v1.json",
+ "visibility": "public",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.queued",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.queued.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.started",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.started.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.succeeded",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.succeeded.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.failed",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.failed.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.cancelled",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.cancelled.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
+ },
+ {
+ "id": "run.job.expired",
+ "version": "1.0.0",
+ "owner": "run",
+ "schema": "events/payloads/run.job.expired.v1.json",
+ "visibility": "first-party",
+ "compatibility": "backward",
+ "status": "planned",
+ "adr": "ADR-034"
}
}
]
]
}
}
new file mode 100644
new file mode 100644
@@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.cancelled.v1.json",
+ "title": "RunJobCancelledPayload",
+ "description": "run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "cancelled",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.expired.v1.json",
+ "title": "RunJobExpiredPayload",
+ "description": "run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "expired",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/error"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,115 @@
@@ -0,0 +1,115 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.failed.v1.json",
+ "title": "RunJobFailedPayload",
+ "description": "run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "failed",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/error"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.queued.v1.json",
+ "title": "RunJobQueuedPayload",
+ "description": "run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "queued",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "null"
+ },
+ "started_at": {
+ "type": "null"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ },
+ "exit": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.started.v1.json",
+ "title": "RunJobStartedPayload",
+ "description": "run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "running",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ },
+ "exit": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,110 @@
@@ -0,0 +1,110 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.succeeded.v1.json",
+ "title": "RunJobSucceededPayload",
+ "description": "run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "succeeded",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "allOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "reason": {
+ "const": "exited"
+ },
+ "code": {
+ "const": 0
+ }
+ }
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,119 @@
@@ -0,0 +1,119 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-create-request.v1.json",
+ "title": "RunJobCreateRequest",
+ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "class",
+ "limits"
+ ],
+ "properties": {
+ "class": {
+ "$ref": "../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "$ref": "#/$defs/artifact"
+ },
+ "args": {
+ "type": "object",
+ "description": "The artifact's declared input as JSON; default {}."
+ },
+ "inputs": {
+ "type": "array",
+ "maxItems": 32,
+ "items": {
+ "$ref": "#/$defs/input"
+ },
+ "description": "Files placed in the job directory before the process starts; names are unique (else 422)."
+ },
+ "limits": {
+ "$ref": "#/$defs/limits"
+ },
+ "ttl_ms": {
+ "type": "integer",
+ "minimum": 1000,
+ "maximum": 86400000,
+ "description": "Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000."
+ },
+ "egress": {
+ "$ref": "#/$defs/egress"
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json"
+ },
+ "idempotency_key": {
+ "$ref": "#/$defs/idempotency_key"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": ["class"],
+ "properties": { "class": { "enum": ["function", "code"] } }
+ },
+ "then": {
+ "required": ["artifact"]
+ }
+ }
+ ],
+ "$defs": {
+ "artifact": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "The pre-registered artifact to run (platform.job@1 artifact).",
+ "required": ["name", "version"],
+ "properties": {
+ "name": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$" },
+ "version": { "type": "string", "pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$", "description": "One exact version, never a range" }
+ }
+ },
+ "input": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "One input file: an OpenVibe.Media object the caller's project can read, pinned by digest.",
+ "required": ["name", "media_id", "sha256"],
+ "properties": {
+ "name": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$", "description": "File name in the job directory; no path separators." },
+ "media_id": { "type": "string", "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$" },
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$", "description": "Lowercase hex SHA-256 of the object's bytes." },
+ "size_bytes": { "type": "integer", "minimum": 0 }
+ }
+ },
+ "limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "Caps on the running process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.",
+ "required": ["wall_ms", "cpu_ms", "mem_bytes"],
+ "properties": {
+ "wall_ms": { "type": "integer", "minimum": 1, "description": "Running time from `job_started`" },
+ "cpu_ms": { "type": "integer", "minimum": 1, "description": "CPU time, user plus system" },
+ "mem_bytes": { "type": "integer", "minimum": 1, "description": "Peak resident memory" }
+ }
+ },
+ "egress": {
+ "type": "string",
+ "enum": ["none", "public", "openvibe-only"],
+ "default": "none",
+ "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
+ },
+ "idempotency_key": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9._:-]{1,128}$"
+ }
+ },
+ "examples": [
+ {
+ "class": "function",
+ "artifact": { "name": "thumbnail", "version": "1.2.0" },
+ "args": { "width": 320 },
+ "inputs": [
+ { "name": "photo.jpg", "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ", "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "size_bytes": 482113 }
+ ],
+ "limits": { "wall_ms": 30000, "cpu_ms": 30000, "mem_bytes": 268435456 },
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-create-result.v1.json",
+ "title": "RunJobCreateResult",
+ "description": "run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created",
+ "job"
+ ],
+ "properties": {
+ "created": {
+ "type": "boolean",
+ "description": "false when the idempotency_key matched an earlier job."
+ },
+ "job": {
+ "$ref": "job-read-result.v1.json"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-list-query.v1.json",
+ "title": "RunJobListQuery",
+ "description": "run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": ["queued", "placed", "running", "succeeded", "failed", "cancelled", "expired"]
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
+ },
+ "limit": {
+ "oneOf": [
+ { "type": "integer", "minimum": 1, "maximum": 200 },
+ { "type": "string", "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$" }
+ ],
+ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
+ },
+ "cursor": {
+ "type": "string",
+ "maxLength": 512,
+ "description": "next_cursor of the previous page."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-list-result.v1.json",
+ "title": "RunJobListResult",
+ "description": "run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "jobs",
+ "next_cursor"
+ ],
+ "properties": {
+ "jobs": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "$ref": "job-read-result.v1.json"
+ }
+ },
+ "next_cursor": {
+ "type": ["string", "null"],
+ "maxLength": 512,
+ "description": "Pass as cursor for the next page; null on the last page."
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,211 @@
@@ -0,0 +1,211 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-read-result.v1.json",
+ "title": "RunJobReadResult",
+ "description": "run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "inputs",
+ "limits",
+ "ttl_ms",
+ "egress",
+ "requirements",
+ "idempotency_key",
+ "placement",
+ "timings",
+ "exit",
+ "result",
+ "usage",
+ "error"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>)."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The project the caller's token names; usage is billed to it."
+ },
+ "requester": {
+ "$ref": "../identity/subject-ref.v1.json",
+ "description": "The token principal that submitted the job."
+ },
+ "state": {
+ "enum": [
+ "queued",
+ "placed",
+ "running",
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ]
+ },
+ "class": {
+ "$ref": "../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ { "$ref": "job-create-request.v1.json#/$defs/artifact" },
+ { "type": "null" }
+ ]
+ },
+ "inputs": {
+ "type": "array",
+ "maxItems": 32,
+ "items": { "$ref": "job-create-request.v1.json#/$defs/input" }
+ },
+ "limits": {
+ "$ref": "job-create-request.v1.json#/$defs/limits"
+ },
+ "ttl_ms": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The job's lifetime from created_at, the default filled in."
+ },
+ "egress": {
+ "$ref": "job-create-request.v1.json#/$defs/egress"
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json",
+ "description": "What the job was placed against, the default filled in."
+ },
+ "idempotency_key": {
+ "anyOf": [
+ { "$ref": "job-create-request.v1.json#/$defs/idempotency_key" },
+ { "type": "null" }
+ ]
+ },
+ "placement": {
+ "anyOf": [
+ { "$ref": "#/$defs/placement" },
+ { "type": "null" }
+ ],
+ "description": "Where the job was sent; null while queued, and for a job cancelled or expired before placement."
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["created_at", "placed_at", "started_at", "finished_at"],
+ "properties": {
+ "created_at": { "type": "string", "format": "date-time" },
+ "placed_at": { "type": ["string", "null"], "format": "date-time" },
+ "started_at": { "type": ["string", "null"], "format": "date-time", "description": "From job_started started_ms; null until the process started." },
+ "finished_at": { "type": ["string", "null"], "format": "date-time", "description": "When the job reached its end state; null before." }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ { "$ref": "#/$defs/exit" },
+ { "type": "null" }
+ ],
+ "description": "The node's job_exit; null until it arrived, and for a job that ended before a node ran it."
+ },
+ "result": {
+ "description": "The artifact's declared output (job_exit result, any JSON); null unless state is succeeded."
+ },
+ "usage": {
+ "$ref": "#/$defs/usage"
+ },
+ "error": {
+ "anyOf": [
+ { "$ref": "#/$defs/error" },
+ { "type": "null" }
+ ],
+ "description": "Why the job failed or expired; null in every other state."
+ }
+ },
+ "$defs": {
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["node", "provider", "region"],
+ "properties": {
+ "node": { "type": "string", "minLength": 1, "maxLength": 128, "description": "The worker's device id (the usage reading's `node`)." },
+ "provider": { "type": ["string", "null"], "maxLength": 128, "description": "The offer's provider (the usage reading's `provider`); null for a first-party node." },
+ "region": { "type": ["string", "null"], "maxLength": 64 }
+ }
+ },
+ "exit": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["reason", "code"],
+ "properties": {
+ "reason": {
+ "enum": ["exited", "cancelled", "ttl", "limit", "stopped", "failed"],
+ "description": "platform.job-frame@1 job_exit reason."
+ },
+ "code": { "type": ["integer", "null"], "description": "The process's exit status; null when it was killed or never ran." }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["seconds", "wall_ms", "cpu_ms", "mem_peak_bytes"],
+ "properties": {
+ "seconds": { "type": "number", "minimum": 0, "description": "Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started." },
+ "wall_ms": { "type": "integer", "minimum": 0 },
+ "cpu_ms": { "type": ["integer", "null"], "minimum": 0, "description": "Informational, never billed." },
+ "mem_peak_bytes": { "type": ["integer", "null"], "minimum": 0, "description": "Informational, never billed." }
+ }
+ },
+ "error": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": ["code", "detail"],
+ "properties": {
+ "code": {
+ "type": "string",
+ "pattern": "^run\\.[a-z0-9_]+(\\.[a-z0-9_]+)*$",
+ "description": "run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl."
+ },
+ "detail": { "type": ["string", "null"], "maxLength": 1024 }
+ }
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "state": { "const": "queued" } }, "required": ["state"] },
+ "then": { "properties": { "placement": { "type": "null" }, "exit": { "type": "null" }, "timings": { "type": "object", "properties": { "placed_at": { "type": "null" }, "started_at": { "type": "null" }, "finished_at": { "type": "null" } } } } }
+ },
+ {
+ "if": { "properties": { "state": { "enum": ["placed", "running"] } }, "required": ["state"] },
+ "then": { "properties": { "placement": { "type": "object" }, "exit": { "type": "null" }, "timings": { "type": "object", "properties": { "placed_at": { "type": "string" }, "finished_at": { "type": "null" } } } } }
+ },
+ {
+ "if": { "properties": { "state": { "const": "running" } }, "required": ["state"] },
+ "then": { "properties": { "timings": { "type": "object", "properties": { "started_at": { "type": "string" } } } } }
+ },
+ {
+ "if": { "properties": { "state": { "enum": ["succeeded", "failed", "cancelled", "expired"] } }, "required": ["state"] },
+ "then": { "properties": { "timings": { "type": "object", "properties": { "finished_at": { "type": "string" } } } } }
+ },
+ {
+ "if": { "properties": { "state": { "const": "succeeded" } }, "required": ["state"] },
+ "then": { "properties": { "placement": { "type": "object" }, "exit": { "type": "object", "properties": { "reason": { "const": "exited" }, "code": { "const": 0 } } }, "timings": { "type": "object", "properties": { "started_at": { "type": "string" } } } } }
+ },
+ {
+ "if": { "properties": { "state": { "enum": ["failed", "expired"] } }, "required": ["state"] },
+ "then": { "properties": { "error": { "type": "object" } } },
+ "else": { "properties": { "error": { "type": "null" } } }
+ },
+ {
+ "if": { "properties": { "state": { "const": "succeeded" } }, "required": ["state"] },
+ "else": { "properties": { "result": { "type": "null" } } }
+ },
+ {
+ "if": { "properties": { "class": { "enum": ["function", "code"] } }, "required": ["class"] },
+ "then": { "properties": { "artifact": { "type": "object" } } }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-stream-event.v1.json",
+ "title": "RunJobStreamEvent",
+ "description": "run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "job_id",
+ "seq",
+ "at"
+ ],
+ "properties": {
+ "type": {
+ "enum": ["output", "state", "end"]
+ },
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The job's own stream counter from 1; the SSE id."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chunk": {
+ "type": "string",
+ "maxLength": 65536,
+ "description": "output only: UTF-8 text."
+ },
+ "state": {
+ "enum": ["queued", "placed", "running", "succeeded", "failed", "cancelled", "expired"],
+ "description": "state and end: the job's state."
+ }
+ },
+ "allOf": [
+ {
+ "if": { "properties": { "type": { "const": "output" } }, "required": ["type"] },
+ "then": { "required": ["chunk"], "not": { "required": ["state"] } }
+ },
+ {
+ "if": { "properties": { "type": { "enum": ["state", "end"] } }, "required": ["type"] },
+ "then": { "required": ["state"], "not": { "required": ["chunk"] } }
+ },
+ {
+ "if": { "properties": { "type": { "const": "end" } }, "required": ["type"] },
+ "then": { "properties": { "state": { "enum": ["succeeded", "failed", "cancelled", "expired"] } } }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-stream-ticket-result.v1.json",
+ "title": "RunJobStreamTicketResult",
+ "description": "run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ticket",
+ "expires_at",
+ "expires_in",
+ "stream_url",
+ "job_id"
+ ],
+ "properties": {
+ "ticket": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
+ "maxLength": 4096,
+ "description": "The signed ticket (a compact JWS)."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_in": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 300,
+ "description": "Seconds until the ticket expires (120)."
+ },
+ "stream_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://[^?#]+/api/v1/jobs/job_[0-9A-HJKMNP-TV-Z]{26}/stream$"
+ },
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
+{
+ "class": "function",
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113,
+ "url": "https://example.com/photo.jpg"
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS"
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "lan",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "^1.2"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "class": "code",
+ "artifact": {
+ "name": "py-runner",
+ "version": "3.12.1"
+ },
+ "args": {
+ "entry": "main.py"
+ },
+ "limits": {
+ "wall_ms": 600000,
+ "cpu_ms": 600000,
+ "mem_bytes": 1073741824
+ },
+ "egress": "public",
+ "requirements": {
+ "kind": "run.code",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "cheapest",
+ "trust": [
+ "first-party",
+ "user-owned"
+ ]
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+{
+ "job": {
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "queued",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
+{
+ "created": true,
+ "job": {
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "queued",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
+{
+ "created": false,
+ "job": {
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "running",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "limit": "201"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "state": "done"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "limit": 200
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
+{
+ "state": "running",
+ "limit": "50",
+ "cursor": "eyJpZCI6ImpvYl8wMSJ9"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
+{
+ "jobs": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
+{
+ "jobs": [],
+ "next_cursor": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,118 @@
@@ -0,0 +1,118 @@
+{
+ "jobs": [
+ {
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "succeeded",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "exited",
+ "code": 0
+ },
+ "result": {
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+ },
+ {
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "queued",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+ }
+ ],
+ "next_cursor": "eyJpZCI6ImpvYl8wMSJ9"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,59 @@
@@ -0,0 +1,59 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "failed",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "limit",
+ "code": null
+ },
+ "result": null,
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "queued",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": {
+ "ok": true
+ },
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "running",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "succeeded",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "exited",
+ "code": 1
+ },
+ "result": {
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "paused",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "expired",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": "2026-10-04T10:10:30.000Z"
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": {
+ "code": "run.job.unplaceable",
+ "detail": "no node offers worker:function with egress none"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "failed",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "limit",
+ "code": null
+ },
+ "result": null,
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": {
+ "code": "run.job.limit",
+ "detail": "mem_bytes exceeded"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "queued",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "running",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": null
+ },
+ "exit": null,
+ "result": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
+{
+ "id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPS",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "ttl_ms": 630000,
+ "egress": "none",
+ "requirements": {
+ "kind": "run.function",
+ "mobility": "job",
+ "latency_class": "background",
+ "objective": "balanced"
+ },
+ "idempotency_key": "thumb:photo-7",
+ "state": "succeeded",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "exited",
+ "code": 0
+ },
+ "result": {
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPW"
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "type": "end",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "seq": 4,
+ "at": "2026-10-04T10:00:03.000Z",
+ "state": "running"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
+{
+ "type": "output",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "seq": 3,
+ "at": "2026-10-04T10:00:01.000Z"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "type": "end",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "seq": 4,
+ "at": "2026-10-04T10:00:03.000Z",
+ "state": "succeeded"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "type": "output",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "seq": 3,
+ "at": "2026-10-04T10:00:01.000Z",
+ "chunk": "resized 1 of 1\n"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "type": "state",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "seq": 2,
+ "at": "2026-10-04T10:00:00.650Z",
+ "state": "running"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "ticket": "sample-ticket.not-a-jwt.placeholder",
+ "expires_at": "2026-10-04T10:02:00.000Z",
+ "expires_in": 3600,
+ "stream_url": "https://openvibe.run/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/stream",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "ticket": "sample-ticket.not-a-jwt.placeholder",
+ "expires_at": "2026-10-04T10:02:00.000Z",
+ "expires_in": 120,
+ "stream_url": "https://openvibe.run/api/v1/jobs",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,7 @@
@@ -0,0 +1,7 @@
+{
+ "ticket": "sample-ticket.not-a-jwt.placeholder",
+ "expires_at": "2026-10-04T10:02:00.000Z",
+ "expires_in": 120,
+ "stream_url": "https://openvibe.run/api/v1/jobs/job_01JAB2C3D4E5F6G7H8J9K0MNPQ/stream",
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ"
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "cancelled",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "cancelled",
+ "code": null
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": {
+ "code": "run.job.limit",
+ "detail": null
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "cancelled",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": "2026-10-04T10:00:05.000Z"
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "cancelled",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "cancelled",
+ "code": null
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "expired",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": {
+ "code": "run.job.unplaceable",
+ "detail": "no node offers worker:function with egress none"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "expired",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": "2026-10-04T10:10:30.000Z"
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": {
+ "code": "run.job.unplaceable",
+ "detail": "no node offers worker:function with egress none"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "failed",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "limit",
+ "code": null
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "failed",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "limit",
+ "code": null
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": {
+ "code": "run.job.limit",
+ "detail": "mem_bytes exceeded"
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "queued",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "queued",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": null,
+ "started_at": null,
+ "finished_at": null
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "running",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": null,
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": null
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "running",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": null
+ },
+ "exit": null,
+ "usage": {
+ "seconds": 0,
+ "wall_ms": 0,
+ "cpu_ms": null,
+ "mem_peak_bytes": null
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "succeeded",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "exited",
+ "code": 2
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
+{
+ "job_id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "project_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPR",
+ "requester": {
+ "type": "app",
+ "id": "app_01JAB2C3D4E5F6G7H8J9K0MNPV"
+ },
+ "state": "succeeded",
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "egress": "none",
+ "placement": {
+ "node": "dev_01JAB2C3D4E5F6G7H8J9K0MNPT",
+ "provider": null,
+ "region": "us-west"
+ },
+ "timings": {
+ "created_at": "2026-10-04T10:00:00.000Z",
+ "placed_at": "2026-10-04T10:00:00.400Z",
+ "started_at": "2026-10-04T10:00:00.650Z",
+ "finished_at": "2026-10-04T10:00:03.000Z"
+ },
+ "exit": {
+ "reason": "exited",
+ "code": 0
+ },
+ "usage": {
+ "seconds": 2.35,
+ "wall_ms": 2350,
+ "cpu_ms": 1800,
+ "mem_peak_bytes": 41943040
+ },
+ "error": null
+}
@@ -63363,6 +63363,1701 @@
@@ -63363,6 +63363,1701 @@
}
}
]
]
}
}
+ },
+ "run.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/run/job-create-request.v1.json",
+ "title": "RunJobCreateRequest",
+ "description": "run.job-create-request@1 (PLANNED; plan T14 R1, ADR-034): the body of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). The job belongs to the project the caller's token names. CLASS is a platform.runtime-class@1 name; Run accepts any of them but places a job only on a node that advertises worker:<class> (platform.resource-offer@1), so a class no node runs today (browser, linux, desktop, gpu) ends expired with run.job.unplaceable. ARTIFACT is the pre-registered artifact to run (name and one exact version), required for function and code, as platform.job@1 carries it. ARGS is the artifact's declared input as JSON (at most 512 KiB serialized, else 413). INPUTS are files placed in the job directory before the process starts, each an OpenVibe.Media object pinned by its sha256: a size or digest mismatch fails the job (run.job.input_digest_mismatch) before the process ever runs; Run never fetches a caller-chosen URL. LIMITS are hard caps merged stricter with the node's own (platform.job@1 limits); a request over the project's tier cap is refused 422 run.limits.exceeded, never silently lowered. EGRESS is the network the job may reach, the values of OpenVibe.Node's worker.egress: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). REQUIREMENTS (platform.workload-requirements@1) narrow placement; absent means { kind: run.<class>, mobility: job, latency_class: background, objective: balanced }. IDEMPOTENCY_KEY: a repeat from the same project within 24 h with an identical body answers the first job (200, created false); with a different body 409 run.idempotency.conflict.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "class",
+ "limits"
+ ],
+ "properties": {
+ "class": {
+ "$ref": "../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "$ref": "#/$defs/artifact"
+ },
+ "args": {
+ "type": "object",
+ "description": "The artifact's declared input as JSON; default {}."
+ },
+ "inputs": {
+ "type": "array",
+ "maxItems": 32,
+ "items": {
+ "$ref": "#/$defs/input"
+ },
+ "description": "Files placed in the job directory before the process starts; names are unique (else 422)."
+ },
+ "limits": {
+ "$ref": "#/$defs/limits"
+ },
+ "ttl_ms": {
+ "type": "integer",
+ "minimum": 1000,
+ "maximum": 86400000,
+ "description": "Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000."
+ },
+ "egress": {
+ "$ref": "#/$defs/egress"
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json"
+ },
+ "idempotency_key": {
+ "$ref": "#/$defs/idempotency_key"
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "required": [
+ "class"
+ ],
+ "properties": {
+ "class": {
+ "enum": [
+ "function",
+ "code"
+ ]
+ }
+ }
+ },
+ "then": {
+ "required": [
+ "artifact"
+ ]
+ }
+ }
+ ],
+ "$defs": {
+ "artifact": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "The pre-registered artifact to run (platform.job@1 artifact).",
+ "required": [
+ "name",
+ "version"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-z0-9][a-z0-9._-]{0,127}$"
+ },
+ "version": {
+ "type": "string",
+ "pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$",
+ "description": "One exact version, never a range"
+ }
+ }
+ },
+ "input": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "One input file: an OpenVibe.Media object the caller's project can read, pinned by digest.",
+ "required": [
+ "name",
+ "media_id",
+ "sha256"
+ ],
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$",
+ "description": "File name in the job directory; no path separators."
+ },
+ "media_id": {
+ "type": "string",
+ "pattern": "^med_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "sha256": {
+ "type": "string",
+ "pattern": "^[0-9a-f]{64}$",
+ "description": "Lowercase hex SHA-256 of the object's bytes."
+ },
+ "size_bytes": {
+ "type": "integer",
+ "minimum": 0
+ }
+ }
+ },
+ "limits": {
+ "type": "object",
+ "additionalProperties": false,
+ "description": "Caps on the running process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.",
+ "required": [
+ "wall_ms",
+ "cpu_ms",
+ "mem_bytes"
+ ],
+ "properties": {
+ "wall_ms": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Running time from `job_started`"
+ },
+ "cpu_ms": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "CPU time, user plus system"
+ },
+ "mem_bytes": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "Peak resident memory"
+ }
+ }
+ },
+ "egress": {
+ "type": "string",
+ "enum": [
+ "none",
+ "public",
+ "openvibe-only"
+ ],
+ "default": "none",
+ "description": "OpenVibe.Node worker.egress values: none (no network), public (the internet without private, link-local or the network's own ranges), openvibe-only (the network's own endpoints only)."
+ },
+ "idempotency_key": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9._:-]{1,128}$"
+ }
+ },
+ "examples": [
+ {
+ "class": "function",
+ "artifact": {
+ "name": "thumbnail",
+ "version": "1.2.0"
+ },
+ "args": {
+ "width": 320
+ },
+ "inputs": [
+ {
+ "name": "photo.jpg",
+ "media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
+ "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "size_bytes": 482113
+ }
+ ],
+ "limits": {
+ "wall_ms": 30000,
+ "cpu_ms": 30000,
+ "mem_bytes": 268435456
+ },
+ "egress": "none",
+ "idempotency_key": "thumb:photo-7"
+ }
+ ]
+ }
+ },
+ "run.job-create-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-create-result.v1.json",
+ "title": "RunJobCreateResult",
+ "description": "run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created",
+ "job"
+ ],
+ "properties": {
+ "created": {
+ "type": "boolean",
+ "description": "false when the idempotency_key matched an earlier job."
+ },
+ "job": {
+ "$ref": "job-read-result.v1.json"
+ }
+ }
+ }
+ },
+ "run.job-read-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-read-result.v1.json",
+ "title": "RunJobReadResult",
+ "description": "run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "inputs",
+ "limits",
+ "ttl_ms",
+ "egress",
+ "requirements",
+ "idempotency_key",
+ "placement",
+ "timings",
+ "exit",
+ "result",
+ "usage",
+ "error"
+ ],
+ "properties": {
+ "id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>)."
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "The project the caller's token names; usage is billed to it."
+ },
+ "requester": {
+ "$ref": "../identity/subject-ref.v1.json",
+ "description": "The token principal that submitted the job."
+ },
+ "state": {
+ "enum": [
+ "queued",
+ "placed",
+ "running",
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ]
+ },
+ "class": {
+ "$ref": "../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "inputs": {
+ "type": "array",
+ "maxItems": 32,
+ "items": {
+ "$ref": "job-create-request.v1.json#/$defs/input"
+ }
+ },
+ "limits": {
+ "$ref": "job-create-request.v1.json#/$defs/limits"
+ },
+ "ttl_ms": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The job's lifetime from created_at, the default filled in."
+ },
+ "egress": {
+ "$ref": "job-create-request.v1.json#/$defs/egress"
+ },
+ "requirements": {
+ "$ref": "../platform/workload-requirements.v1.json",
+ "description": "What the job was placed against, the default filled in."
+ },
+ "idempotency_key": {
+ "anyOf": [
+ {
+ "$ref": "job-create-request.v1.json#/$defs/idempotency_key"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Where the job was sent; null while queued, and for a job cancelled or expired before placement."
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "From job_started started_ms; null until the process started."
+ },
+ "finished_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time",
+ "description": "When the job reached its end state; null before."
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "The node's job_exit; null until it arrived, and for a job that ended before a node ran it."
+ },
+ "result": {
+ "description": "The artifact's declared output (job_exit result, any JSON); null unless state is succeeded."
+ },
+ "usage": {
+ "$ref": "#/$defs/usage"
+ },
+ "error": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/error"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Why the job failed or expired; null in every other state."
+ }
+ },
+ "$defs": {
+ "placement": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "node",
+ "provider",
+ "region"
+ ],
+ "properties": {
+ "node": {
+ "type": "string",
+ "minLength": 1,
+ "maxLength": 128,
+ "description": "The worker's device id (the usage reading's `node`)."
+ },
+ "provider": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 128,
+ "description": "The offer's provider (the usage reading's `provider`); null for a first-party node."
+ },
+ "region": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 64
+ }
+ }
+ },
+ "exit": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "reason",
+ "code"
+ ],
+ "properties": {
+ "reason": {
+ "enum": [
+ "exited",
+ "cancelled",
+ "ttl",
+ "limit",
+ "stopped",
+ "failed"
+ ],
+ "description": "platform.job-frame@1 job_exit reason."
+ },
+ "code": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "description": "The process's exit status; null when it was killed or never ran."
+ }
+ }
+ },
+ "usage": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "seconds",
+ "wall_ms",
+ "cpu_ms",
+ "mem_peak_bytes"
+ ],
+ "properties": {
+ "seconds": {
+ "type": "number",
+ "minimum": 0,
+ "description": "Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started."
+ },
+ "wall_ms": {
+ "type": "integer",
+ "minimum": 0
+ },
+ "cpu_ms": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 0,
+ "description": "Informational, never billed."
+ },
+ "mem_peak_bytes": {
+ "type": [
+ "integer",
+ "null"
+ ],
+ "minimum": 0,
+ "description": "Informational, never billed."
+ }
+ }
+ },
+ "error": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "code",
+ "detail"
+ ],
+ "properties": {
+ "code": {
+ "type": "string",
+ "pattern": "^run\\.[a-z0-9_]+(\\.[a-z0-9_]+)*$",
+ "description": "run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl."
+ },
+ "detail": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 1024
+ }
+ }
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "queued"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "null"
+ },
+ "exit": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "placed_at": {
+ "type": "null"
+ },
+ "started_at": {
+ "type": "null"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "enum": [
+ "placed",
+ "running"
+ ]
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ },
+ "exit": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "placed_at": {
+ "type": "string"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "running"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "timings": {
+ "type": "object",
+ "properties": {
+ "started_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "enum": [
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ]
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "timings": {
+ "type": "object",
+ "properties": {
+ "finished_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "succeeded"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "placement": {
+ "type": "object"
+ },
+ "exit": {
+ "type": "object",
+ "properties": {
+ "reason": {
+ "const": "exited"
+ },
+ "code": {
+ "const": 0
+ }
+ }
+ },
+ "timings": {
+ "type": "object",
+ "properties": {
+ "started_at": {
+ "type": "string"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "enum": [
+ "failed",
+ "expired"
+ ]
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "then": {
+ "properties": {
+ "error": {
+ "type": "object"
+ }
+ }
+ },
+ "else": {
+ "properties": {
+ "error": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "state": {
+ "const": "succeeded"
+ }
+ },
+ "required": [
+ "state"
+ ]
+ },
+ "else": {
+ "properties": {
+ "result": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "class": {
+ "enum": [
+ "function",
+ "code"
+ ]
+ }
+ },
+ "required": [
+ "class"
+ ]
+ },
+ "then": {
+ "properties": {
+ "artifact": {
+ "type": "object"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "run.job-list-query": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-list-query.v1.json",
+ "title": "RunJobListQuery",
+ "description": "run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.",
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "state": {
+ "enum": [
+ "queued",
+ "placed",
+ "running",
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ]
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$",
+ "description": "Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403."
+ },
+ "limit": {
+ "oneOf": [
+ {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 200
+ },
+ {
+ "type": "string",
+ "pattern": "^(?:[1-9][0-9]?|1[0-9]{2}|200)$"
+ }
+ ],
+ "description": "Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range."
+ },
+ "cursor": {
+ "type": "string",
+ "maxLength": 512,
+ "description": "next_cursor of the previous page."
+ }
+ }
+ }
+ },
+ "run.job-list-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-list-result.v1.json",
+ "title": "RunJobListResult",
+ "description": "run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "jobs",
+ "next_cursor"
+ ],
+ "properties": {
+ "jobs": {
+ "type": "array",
+ "maxItems": 200,
+ "items": {
+ "$ref": "job-read-result.v1.json"
+ }
+ },
+ "next_cursor": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "maxLength": 512,
+ "description": "Pass as cursor for the next page; null on the last page."
+ }
+ }
+ }
+ },
+ "run.job-stream-ticket-result": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-stream-ticket-result.v1.json",
+ "title": "RunJobStreamTicketResult",
+ "description": "run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "ticket",
+ "expires_at",
+ "expires_in",
+ "stream_url",
+ "job_id"
+ ],
+ "properties": {
+ "ticket": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$",
+ "maxLength": 4096,
+ "description": "The signed ticket (a compact JWS)."
+ },
+ "expires_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_in": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 300,
+ "description": "Seconds until the ticket expires (120)."
+ },
+ "stream_url": {
+ "type": "string",
+ "format": "uri",
+ "pattern": "^https?://[^?#]+/api/v1/jobs/job_[0-9A-HJKMNP-TV-Z]{26}/stream$"
+ },
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ }
+ }
+ }
+ },
+ "run.job-stream-event": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/run/job-stream-event.v1.json",
+ "title": "RunJobStreamEvent",
+ "description": "run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "type",
+ "job_id",
+ "seq",
+ "at"
+ ],
+ "properties": {
+ "type": {
+ "enum": [
+ "output",
+ "state",
+ "end"
+ ]
+ },
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "seq": {
+ "type": "integer",
+ "minimum": 1,
+ "description": "The job's own stream counter from 1; the SSE id."
+ },
+ "at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "chunk": {
+ "type": "string",
+ "maxLength": 65536,
+ "description": "output only: UTF-8 text."
+ },
+ "state": {
+ "enum": [
+ "queued",
+ "placed",
+ "running",
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ],
+ "description": "state and end: the job's state."
+ }
+ },
+ "allOf": [
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "output"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "required": [
+ "chunk"
+ ],
+ "not": {
+ "required": [
+ "state"
+ ]
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "enum": [
+ "state",
+ "end"
+ ]
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "required": [
+ "state"
+ ],
+ "not": {
+ "required": [
+ "chunk"
+ ]
+ }
+ }
+ },
+ {
+ "if": {
+ "properties": {
+ "type": {
+ "const": "end"
+ }
+ },
+ "required": [
+ "type"
+ ]
+ },
+ "then": {
+ "properties": {
+ "state": {
+ "enum": [
+ "succeeded",
+ "failed",
+ "cancelled",
+ "expired"
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "run.job.queued": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.queued.v1.json",
+ "title": "RunJobQueuedPayload",
+ "description": "run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "queued",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "type": "null"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "null"
+ },
+ "started_at": {
+ "type": "null"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ },
+ "exit": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ "run.job.started": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.started.v1.json",
+ "title": "RunJobStartedPayload",
+ "description": "run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "running",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "null"
+ }
+ }
+ },
+ "exit": {
+ "type": "null"
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ "run.job.succeeded": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.succeeded.v1.json",
+ "title": "RunJobSucceededPayload",
+ "description": "run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "succeeded",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "allOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "reason": {
+ "const": "exited"
+ },
+ "code": {
+ "const": 0
+ }
+ }
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ "run.job.failed": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.failed.v1.json",
+ "title": "RunJobFailedPayload",
+ "description": "run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "failed",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/error"
+ }
+ }
+ }
+ },
+ "run.job.cancelled": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.cancelled.v1.json",
+ "title": "RunJobCancelledPayload",
+ "description": "run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "cancelled",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "type": "null"
+ }
+ }
+ }
+ },
+ "run.job.expired": {
+ "version": "1.0.0",
+ "owner": "run",
+ "status": "planned",
+ "schema": {
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://openvibe.network/contracts/events/payloads/run.job.expired.v1.json",
+ "title": "RunJobExpiredPayload",
+ "description": "run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.",
+ "type": "object",
+ "required": [
+ "job_id",
+ "project_id",
+ "requester",
+ "state",
+ "class",
+ "artifact",
+ "egress",
+ "placement",
+ "timings",
+ "exit",
+ "usage",
+ "error"
+ ],
+ "additionalProperties": false,
+ "properties": {
+ "job_id": {
+ "type": "string",
+ "pattern": "^job_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "project_id": {
+ "type": "string",
+ "pattern": "^prj_[0-9A-HJKMNP-TV-Z]{26}$"
+ },
+ "requester": {
+ "$ref": "../../identity/subject-ref.v1.json"
+ },
+ "state": {
+ "const": "expired",
+ "description": "The job state (run.job-read-result@1 state)."
+ },
+ "class": {
+ "$ref": "../../platform/runtime-class.v1.json"
+ },
+ "artifact": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/artifact"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "egress": {
+ "$ref": "../../run/job-create-request.v1.json#/$defs/egress"
+ },
+ "placement": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/placement"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "timings": {
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "created_at",
+ "placed_at",
+ "started_at",
+ "finished_at"
+ ],
+ "properties": {
+ "created_at": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "placed_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "started_at": {
+ "type": [
+ "string",
+ "null"
+ ],
+ "format": "date-time"
+ },
+ "finished_at": {
+ "type": "string",
+ "format": "date-time"
+ }
+ }
+ },
+ "exit": {
+ "anyOf": [
+ {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/exit"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "usage": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/usage"
+ },
+ "error": {
+ "$ref": "../../run/job-read-result.v1.json#/$defs/error"
+ }
+ }
+ }
}
}
}
}
}
}
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"title": "OpenVibe.AI API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"title": "OpenVibe.Billing API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"title": "OpenVibe.Blog API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Bot API",
"title": "OpenVibe.Bot API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Bot as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"title": "OpenVibe.Chat API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"title": "OpenVibe.Codes API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"title": "OpenVibe.Community API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"title": "OpenVibe.Coupons API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"title": "OpenVibe.Deals API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"title": "OpenVibe.Events API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"title": "OpenVibe.Games API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"title": "OpenVibe.Host API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"title": "OpenVibe.Live API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"title": "OpenVibe.Media API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"title": "OpenVibe.Network API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"title": "OpenVibe.News API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRe.Stream API",
"title": "OpenRe.Stream API",
- "version": "0.89.0",
- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"title": "OpenVibe.Reviews API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"title": "OpenVibe.Search API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"title": "OpenVibe.Sources API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Space API",
"title": "OpenVibe.Space API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Space as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"title": "OpenVibe.Tips API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"title": "OpenVibe.Tools API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"title": "OpenVibe.Trade API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"title": "OpenVibe.VIP API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"title": "OpenVibe.Wiki API",
- "version": "0.89.0",
- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.89.0): each operation lists the capabilities it performs; a token for 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.90.0",
+ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.90.0): each operation lists the capabilities it performs; a token for 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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -52408,3 +52408,700 @@ export type BotDeviceConnectResult =
@@ -52408,3 +52408,700 @@ export type BotDeviceConnectResult =
| {
| {
device: BotDevice;
device: BotDevice;
};
};
+
+/** [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. 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: none (default; any node, sent as net deny), public or openvibe-only (only a node whose worker.egress is exactly that policy, until platform.job@1 net carries it per job). 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.
+ */
+export type RunJobCreateRequest = {
+ [k: string]: unknown | undefined;
+} & {
+ class: RuntimeClass;
+ /**
+ * The pre-registered artifact to run (platform.job@1 artifact).
+ */
+ artifact?: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ };
+ /**
+ * The artifact's declared input as JSON; default {}.
+ */
+ args?: {};
+ /**
+ * Files placed in the job directory before the process starts; names are unique (else 422).
+ *
+ * @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 process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.
+ */
+ limits: {
+ /**
+ * Running time from `job_started`
+ */
+ wall_ms: number;
+ /**
+ * CPU time, user plus system
+ */
+ cpu_ms: number;
+ /**
+ * Peak resident memory
+ */
+ mem_bytes: number;
+ };
+ /**
+ * Lifetime from creation, covering the wait for a node and the run (platform.job@1 ttl_ms); when it runs out the job ends expired. Default: limits.wall_ms plus 600000.
+ */
+ ttl_ms?: 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";
+ requirements?: WorkloadRequirements;
+ idempotency_key?: string;
+};
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-create-result@1 (PLANNED; plan T14 R1, ADR-034): the answer of POST /api/v1/jobs on OpenVibe.Run (capability run.job.submit). 201 with created true and the new job, queued; 200 with created false and the job first created under the same idempotency_key (in whatever state it has reached). Refusals are application/problem+json: 400 (body invalid), 413 (args over 512 KiB), 422 run.limits.exceeded or a duplicate input name, 409 run.idempotency.conflict, 429 (quota).
+ */
+export interface RunJobCreateResult {
+ /**
+ * false when the idempotency_key matched an earlier job.
+ */
+ created: boolean;
+ job: RunJobReadResult;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-read-result@1 (PLANNED; plan T14 R1, ADR-034): one job on OpenVibe.Run, as GET /api/v1/jobs/{id} answers it (capability run.job.read), POST /api/v1/jobs/{id}/cancel answers it after the cancel (run.job.cancel), and run.job-create-result@1 and run.job-list-result@1 carry it. STATES: queued (stored, waiting for a node) -> placed (sent to a node as platform.job@1, not yet started) -> running (the node sent job_started) -> one end state: succeeded (job_exit reason exited with code 0), failed (exited with another code, or reason limit, stopped or failed), cancelled (POST /api/v1/jobs/{id}/cancel, or job_exit reason cancelled), expired (ttl_ms ran out: before placement, before start, or while running, job_exit reason ttl). An end state never changes. Run mints the id (job_<ULID>) and sends it unchanged as platform.job@1 `id`, so every metering reading of the job is `run:<id>:<n>` (platform.job-frame@1 job_usage). usage.seconds is wall_ms / 1000: the sum of the job's readings (service run, operation function.invoke, unit s). Never carries the job's stdout (GET /api/v1/jobs/{id}/stream) or a credential. More fields may be added.
+ */
+export type RunJobReadResult = {
+ [k: string]: unknown | undefined;
+} & {
+ /**
+ * Minted by Run; the platform.job@1 id and the stem of every usage reading key (run:<id>:<n>).
+ */
+ id: string;
+ /**
+ * The project the caller's token names; usage is billed to it.
+ */
+ project_id: string;
+ requester: SubjectRef;
+ state: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * @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 process (platform.job@1 limits); hitting one ends the job failed with run.job.limit.
+ */
+ limits: {
+ /**
+ * Running time from `job_started`
+ */
+ wall_ms: number;
+ /**
+ * CPU time, user plus system
+ */
+ cpu_ms: number;
+ /**
+ * Peak resident memory
+ */
+ mem_bytes: number;
+ };
+ /**
+ * The job's lifetime from created_at, the default filled in.
+ */
+ ttl_ms: 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";
+ requirements: WorkloadRequirements;
+ idempotency_key: string | null;
+ /**
+ * Where the job was sent; null while queued, and for a job cancelled or expired before placement.
+ */
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ } | null;
+ timings: {
+ created_at: string;
+ placed_at: string | null;
+ /**
+ * From job_started started_ms; null until the process started.
+ */
+ started_at: string | null;
+ /**
+ * When the job reached its end state; null before.
+ */
+ finished_at: string | null;
+ };
+ /**
+ * The node's job_exit; null until it arrived, and for a job that ended before a node ran it.
+ */
+ exit: {
+ /**
+ * platform.job-frame@1 job_exit reason.
+ */
+ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";
+ /**
+ * The process's exit status; null when it was killed or never ran.
+ */
+ code: number | null;
+ } | null;
+ /**
+ * The artifact's declared output (job_exit result, any JSON); null unless state is succeeded.
+ */
+ result: {
+ [k: string]: unknown | undefined;
+ };
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ /**
+ * Why the job failed or expired; null in every other state.
+ */
+ error: {
+ /**
+ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.
+ */
+ code: string;
+ detail: string | null;
+ } | null;
+};
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-list-query@1 (PLANNED; plan T14 R1): the query string of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and of GET /api/v1/admin/jobs (run.job.admin, which alone may pass project_id). Without project_id it lists the caller's token project.
+ */
+export interface RunJobListQuery {
+ state?: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
+ /**
+ * Admin only (run.job.admin): list this project's jobs; with run.job.list it is refused 403.
+ */
+ project_id?: string;
+ /**
+ * Items per page, 1 to 200 (default 50). A query string carries it as a string, which must name the same range.
+ */
+ limit?: number | string;
+ /**
+ * next_cursor of the previous page.
+ */
+ cursor?: string;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-list-result@1 (PLANNED; plan T14 R1): the answer of GET /api/v1/jobs on OpenVibe.Run (capability run.job.list) and GET /api/v1/admin/jobs (run.job.admin), filtered by run.job-list-query@1. Jobs come newest first (by id). More fields may be added.
+ */
+export interface RunJobListResult {
+ /**
+ * @maxItems 200
+ */
+ jobs: RunJobReadResult[];
+ /**
+ * Pass as cursor for the next page; null on the last page.
+ */
+ next_cursor: string | null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-stream-ticket-result@1 (PLANNED; plan T14 R1): the answer of POST /api/v1/jobs/{id}/stream/ticket on OpenVibe.Run (capability run.job.stream), modelled on Events' realtime ticket (network.realtime-ticket-result@1). A browser cannot put a header on an EventSource, so it opens `${stream_url}?ticket=${ticket}` (plus Last-Event-ID when it resumes) at once. The ticket is a two-minute RS256 JWT that Run signs: audience openvibe.run, typ run-stream (so it is never a session token), it names this one job, and Run refuses a jti it has already seen while the ticket is valid, so a ticket opens one stream; ask for a fresh one for every reconnect. Never logged, never stored. A server caller may instead send its Bearer token to GET /api/v1/jobs/{id}/stream. The stream is text/event-stream of run.job-stream-event@1.
+ */
+export interface RunJobStreamTicketResult {
+ /**
+ * The signed ticket (a compact JWS).
+ */
+ ticket: string;
+ expires_at: string;
+ /**
+ * Seconds until the ticket expires (120).
+ */
+ expires_in: number;
+ stream_url: string;
+ job_id: string;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job-stream-event@1 (PLANNED; plan T14 R1): the JSON `data` of one server-sent event on GET /api/v1/jobs/{id}/stream (capability run.job.stream). The SSE `event` field is the type and the SSE `id` is `seq`, so a reconnect with Last-Event-ID resumes after it. output: a chunk of the job's stdout (platform.job-frame@1 job_stdout, best effort: a chunk lost on the node's link is never resent, and Run keeps at most the last 1 MiB to replay). state: the job moved to a new state. end: the job reached its end state; the server closes the stream after it. A stream opened on an ended job replays what is kept, then end. Output is never metered and never in an event.
+ */
+export type RunJobStreamEvent = {
+ [k: string]: unknown | undefined;
+} & {
+ type: "output" | "state" | "end";
+ job_id: string;
+ /**
+ * The job's own stream counter from 1; the SSE id.
+ */
+ seq: number;
+ at: string;
+ /**
+ * output only: UTF-8 text.
+ */
+ chunk?: string;
+ /**
+ * state and end: the job's state.
+ */
+ state?: "queued" | "placed" | "running" | "succeeded" | "failed" | "cancelled" | "expired";
+};
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.queued v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). A job was stored queued (POST /api/v1/jobs created it; not published for an idempotent repeat). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.
+ */
+export interface RunJobQueuedPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "queued";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: null;
+ timings: {
+ created_at: string;
+ placed_at: null;
+ started_at: null;
+ finished_at: null;
+ };
+ exit: null;
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.started v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The node sent job_started: the process runs. Usage seconds count from started_at. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority low, actor service:run.
+ */
+export interface RunJobStartedPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "running";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ };
+ timings: {
+ created_at: string;
+ placed_at: string;
+ started_at: string;
+ finished_at: null;
+ };
+ exit: null;
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.succeeded v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended succeeded: job_exit reason exited with code 0. Carries no result (read it with GET /api/v1/jobs/{id}). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.
+ */
+export interface RunJobSucceededPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "succeeded";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ };
+ timings: {
+ created_at: string;
+ placed_at: string;
+ started_at: string;
+ finished_at: string;
+ };
+ exit: {
+ /**
+ * platform.job-frame@1 job_exit reason.
+ */
+ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";
+ /**
+ * The process's exit status; null when it was killed or never ran.
+ */
+ code: number | null;
+ } & {
+ reason?: "exited";
+ code?: 0;
+ };
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.failed v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended failed: job_exit with a non-zero code or reason limit, stopped or failed, or an input digest mismatch before the process started. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority important, actor service:run.
+ */
+export interface RunJobFailedPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "failed";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ } | null;
+ timings: {
+ created_at: string;
+ placed_at: string | null;
+ started_at: string | null;
+ finished_at: string;
+ };
+ exit: {
+ /**
+ * platform.job-frame@1 job_exit reason.
+ */
+ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";
+ /**
+ * The process's exit status; null when it was killed or never ran.
+ */
+ code: number | null;
+ } | null;
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: {
+ /**
+ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.
+ */
+ code: string;
+ detail: string | null;
+ };
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.cancelled v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended cancelled (POST /api/v1/jobs/{id}/cancel by its project or run.job.admin, or job_exit reason cancelled). A job cancelled before placement has no placement and no exit. A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.
+ */
+export interface RunJobCancelledPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "cancelled";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ } | null;
+ timings: {
+ created_at: string;
+ placed_at: string | null;
+ started_at: string | null;
+ finished_at: string;
+ };
+ exit: {
+ /**
+ * platform.job-frame@1 job_exit reason.
+ */
+ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";
+ /**
+ * The process's exit status; null when it was killed or never ran.
+ */
+ code: number | null;
+ } | null;
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: null;
+}
+
+/** [email protected] (owner: run) */
+/**
+ * run.job.expired v1 (OpenVibe.Run; PLANNED: the Run service does not exist yet, plan T14 R1). The job ended expired: its ttl_ms ran out before a node took it (run.job.unplaceable), before it started, or while it ran (job_exit reason ttl, run.job.ttl). A projection of run.job-read-result@1 without args, inputs, result or output. Envelope: subject { type: job, id: <job_id> }, visibility internal, priority normal, actor service:run.
+ */
+export interface RunJobExpiredPayload {
+ job_id: string;
+ project_id: string;
+ requester: SubjectRef;
+ /**
+ * The job state (run.job-read-result@1 state).
+ */
+ state: "expired";
+ class: RuntimeClass;
+ artifact: {
+ name: string;
+ /**
+ * One exact version, never a range
+ */
+ version: string;
+ } | null;
+ /**
+ * 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";
+ placement: {
+ /**
+ * The worker's device id (the usage reading's `node`).
+ */
+ node: string;
+ /**
+ * The offer's provider (the usage reading's `provider`); null for a first-party node.
+ */
+ provider: string | null;
+ region: string | null;
+ } | null;
+ timings: {
+ created_at: string;
+ placed_at: string | null;
+ started_at: string | null;
+ finished_at: string;
+ };
+ exit: {
+ /**
+ * platform.job-frame@1 job_exit reason.
+ */
+ reason: "exited" | "cancelled" | "ttl" | "limit" | "stopped" | "failed";
+ /**
+ * The process's exit status; null when it was killed or never ran.
+ */
+ code: number | null;
+ } | null;
+ usage: {
+ /**
+ * Metered wall-clock seconds, wall_ms / 1000 (unit s); 0 when the job never started.
+ */
+ seconds: number;
+ wall_ms: number;
+ /**
+ * Informational, never billed.
+ */
+ cpu_ms: number | null;
+ /**
+ * Informational, never billed.
+ */
+ mem_peak_bytes: number | null;
+ };
+ error: {
+ /**
+ * run.job.exit_nonzero, run.job.limit, run.job.stopped, run.job.worker_failed, run.job.input_digest_mismatch, run.job.unplaceable (expired: no node offered the class, egress and requirements), run.job.ttl.
+ */
+ code: string;
+ detail: string | null;
+ };
+}
@@ -6,7 +6,7 @@
@@ -6,7 +6,7 @@
const crypto = require('crypto');
const crypto = require('crypto');
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
const ALPHABET = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
-const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', watch: 'wch', observation: 'wco', check: 'ckr' };
+const PREFIX = { user: 'usr', guest: 'gst', app: 'app', mod: 'mod', agent: 'agt', node: 'nod', event: 'evt', media: 'med', project: 'prj', confirmation: 'cnf', job: 'job', watch: 'wch', observation: 'wco', check: 'ckr' };
const SLUG_TYPES = new Set(['service', 'system']);
const SLUG_TYPES = new Set(['service', 'system']);
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
const ULID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/;
const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;
const SLUG_RE = /^[a-z][a-z0-9-]{1,39}$/;
new file mode 100644
new file mode 100644
@@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
+{
+ "id": "run.job.admin",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "internal",
+ "sensitive": true,
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Operator access to every project's jobs: GET /api/v1/admin/jobs (run.job-list-query@1 with project_id), and the read and cancel routes for any job. Never delegated and never in an app or mod token. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "run.job-list-query@1",
+ "outputSchema": "run.job-list-result@1",
+ "permissions": [
+ "run:admin"
+ ],
+ "resourceConstraints": [
+ "none"
+ ],
+ "quotaClass": "run-admin",
+ "events": [
+ "run.job.cancelled"
+ ],
+ "implementedBy": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
+{
+ "id": "run.job.cancel",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "public",
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Cancel one of the token project's jobs: POST /api/v1/jobs/:id/cancel. A queued or placed job never starts; a running one is killed (platform.job-frame@1 job_cancel); cancelling an ended job changes nothing and answers it as it is. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "run.job-read-result@1",
+ "permissions": [
+ "run:cancel"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "run-control",
+ "events": [
+ "run.job.cancelled"
+ ],
+ "implementedBy": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "run.job.list",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "public",
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): List the token project's jobs, newest first and cursor-paged: GET /api/v1/jobs?state=&limit=&cursor=. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "run.job-list-query@1",
+ "outputSchema": "run.job-list-result@1",
+ "permissions": [
+ "run:read"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "run-read",
+ "events": [],
+ "implementedBy": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "run.job.read",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "public",
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Read one of the token project's jobs: GET /api/v1/jobs/:id (state, placement, timings, exit, usage seconds, result). It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "run.job-read-result@1",
+ "permissions": [
+ "run:read"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "run-read",
+ "events": [],
+ "implementedBy": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
+{
+ "id": "run.job.stream",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "public",
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Watch a job's live output: POST /api/v1/jobs/:id/stream/ticket mints a one-use, two-minute stream ticket (run.job-stream-ticket-result@1) and GET /api/v1/jobs/:id/stream?ticket= (or with a Bearer token) is text/event-stream of run.job-stream-event@1, resumable with Last-Event-ID. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "common.no-body@1",
+ "outputSchema": "run.job-stream-ticket-result@1",
+ "permissions": [
+ "run:stream"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "run-stream",
+ "events": [],
+ "implementedBy": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
+{
+ "id": "run.job.submit",
+ "version": "1.0",
+ "owner": "run",
+ "status": "planned",
+ "visibility": "public",
+ "sensitive": true,
+ "description": "PLANNED (plan T14 R1; OpenVibe.Run is not built yet): Submit a job to OpenVibe.Run: POST /api/v1/jobs. The job belongs to the token's project, which is billed per wall-clock second it runs (service run, operation function.invoke, unit s). An idempotency_key makes a retry return the first job. It becomes active, and enters generated/openapi/run.json, when OpenVibe.Run implements the routes.",
+ "inputSchema": "run.job-create-request@1",
+ "outputSchema": "run.job-create-result@1",
+ "permissions": [
+ "run:submit"
+ ],
+ "resourceConstraints": [
+ "project"
+ ],
+ "quotaClass": "run-submit",
+ "events": [
+ "run.job.queued",
+ "run.job.started",
+ "run.job.succeeded",
+ "run.job.failed",
+ "run.job.expired"
+ ],
+ "implementedBy": []
+}
@@ -27,6 +27,7 @@
@@ -27,6 +27,7 @@
"keywords": "openvibe run, code sandbox, run code online, serverless functions, ai agent tasks",
"keywords": "openvibe run, code sandbox, run code online, serverless functions, ai agent tasks",
"launch": "the sandbox runtime",
"launch": "the sandbox runtime",
"relationships": {
"relationships": {
+ "service": "run",
"noRepo": true,
"noRepo": true,
"meanwhile": [
"meanwhile": [
{
{
new file mode 100644
new file mode 100644
@@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
+{
+ "version": "0.0.0",
+ "contractRanges": {
+ "openvibe-contracts": ">=0.90.0 <1.0.0"
+ },
+ "id": "run",
+ "name": "OpenVibe.Run",
+ "status": "placeholder",
+ "repository": "OpenVibers/OpenVibe.Run",
+ "domains": [
+ "openvibe.run"
+ ],
+ "publicOrigin": "https://openvibe.run",
+ "exposure": {
+ "state": "placeholder",
+ "publicSite": null,
+ "note": "planned (plan T14 R1); the OpenVibe.Run repository does not exist yet, and nothing serves openvibe.run"
+ },
+ "capabilities": [
+ "run.job.submit",
+ "run.job.read",
+ "run.job.list",
+ "run.job.cancel",
+ "run.job.stream",
+ "run.job.admin"
+ ],
+ "eventsProduced": [
+ "run.job.queued",
+ "run.job.started",
+ "run.job.succeeded",
+ "run.job.failed",
+ "run.job.cancelled",
+ "run.job.expired"
+ ],
+ "eventsConsumed": [],
+ "namespacesOwned": [],
+ "lifecycle": {
+ "liveness": {
+ "none": "not built yet; nothing listens on a loopback address"
+ },
+ "shutdown": {
+ "none": "not built yet; nothing runs or drains"
+ },
+ "startupRecovery": {
+ "none": "not built yet; nothing starts"
+ },
+ "rollback": {
+ "none": "not built yet; nothing is released or rolled back"
+ },
+ "contracts": {
+ "range": ">=0.90.0 <1.0.0"
+ },
+ "leases": {
+ "none": "not built yet; nothing runs"
+ }
+ },
+ "notes": "The job service of the network (plan T14, ADR-034): accepts jobs (run.job-create-request@1), places each on a node that advertises worker:<class> (platform.resource-offer@1, platform.runtime-offer@1, platform.workload-requirements@1), sends it as platform.job@1 over the node link and follows its platform.job-frame@1 frames. Consumes billing.usage.record to write one usage reading per wall-clock second (run:<job id>:<n>, service run, operation function.invoke, unit s) and media.object.read to resolve digest-pinned inputs. Planned: nothing is built."
+}
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.89.0",
+ "version": "0.90.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.89.0",
+ "version": "0.90.0",
"license": "AGPL-3.0-only",
"license": "AGPL-3.0-only",
"dependencies": {
"dependencies": {
"ajv": "^8.17.1",
"ajv": "^8.17.1",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"name": "openvibe-contracts",
- "version": "0.89.0",
+ "version": "0.90.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",
@@ -74,4 +74,25 @@ checks += 3;
@@ -74,4 +74,25 @@ checks += 3;
// job_usage only ever stands for a full second: it has no quantity of its own.
// job_usage only ever stands for a full second: it has no quantity of its own.
ok(!contracts.validate('platform.job-frame', { ...usage, quantity: 0.5 }).valid, 'a job_usage frame cannot carry a partial quantity');
ok(!contracts.validate('platform.job-frame', { ...usage, quantity: 0.5 }).valid, 'a job_usage frame cannot carry a partial quantity');
+// OpenVibe.Run's job API (T14 R1): Run mints the platform.job@1 id, so its job ids, artifacts and limits are the
+// job's own; a job's usage seconds are the sum of its readings; every run.* capability and event is Run's.
+const create = contracts.schema('run.job-create-request');
+const job = contracts.schema('platform.job');
+ok(contracts.schema('run.job-read-result').properties.id.pattern === job.properties.id.pattern, 'a Run job id is a platform.job@1 id');
+assert.deepStrictEqual(create.$defs.artifact.properties, job.properties.artifact.properties);
+assert.deepStrictEqual(create.$defs.limits.properties, job.properties.limits.properties);
+assert.deepStrictEqual(create.$defs.egress.enum, ['none', 'public', 'openvibe-only']);
+checks += 3;
+ok(contracts.ids.newId('job').match(job.properties.id.pattern), 'newId(job) mints a platform.job@1 id');
+const succeeded = fixture('run.job-read-result', 'valid', 'succeeded');
+ok(succeeded.usage.seconds === fromExit(exit).reduce((s, r) => s + r.quantity, 0) && succeeded.usage.wall_ms === exit.usage.wall_ms, 'usage.seconds is the sum of the job\'s readings');
+const run = contracts.services.get('run');
+ok(run && run.status === 'placeholder' && contracts.products.get('openvibe.run').relationships.noRepo === true, 'run is a placeholder service and openvibe.run still has no repository');
+for (const id of ['run.job.submit', 'run.job.read', 'run.job.list', 'run.job.cancel', 'run.job.stream', 'run.job.admin']) {
+ const c = contracts.capabilities.get(id);
+ ok(c && c.owner === 'run' && c.status === 'planned' && c.description.startsWith('PLANNED') && run.capabilities.includes(id), `${id} is a planned capability Run serves`);
+}
+ok(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}`);
+
console.log(`platform run: ${checks} job, frame and metering checks passed`);
console.log(`platform run: ${checks} job, frame and metering checks passed`);
@@ -529,7 +529,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =
@@ -529,7 +529,7 @@ ok(nodeId.startsWith('nod_') && ids.principalSub({ type: 'node', id: nodeId }) =
assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);
assert.throws(() => ids.principalSub({ type: 'node', id: 'nod_42' }), /principals/);
ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');
ok(!ids.SUBJECT_TYPES.includes('node') && ids.parseSubject(`node:${nodeId}`) === null, 'a node is a principal, never a subject');
assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);
assert.throws(() => ids.principalSub({ type: 'user', id: ids.newId('user') }), /principals/);
-for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);
+for (const [kind, prefix] of [['watch', 'wch'], ['observation', 'wco'], ['check', 'ckr'], ['job', 'job']]) ok(ids.newId(kind).startsWith(`${prefix}_`), `newId(${kind}) is ${prefix}_<ULID>`);
ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');
ok(contracts.validate('media.media-ref', { media_id: ids.legacyMediaId('live', 'vod', 42) }).valid, 'legacyMediaId is a valid MediaRef');
ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');
ok(contracts.validate('events.event-envelope', { event_id: ids.newId('event'), event_type: 'network.user.created', version: 1, source: 'network', actor: { type: 'system', id: 'network' }, timestamp: new Date().toISOString(), subject: { type: 'user', id: ids.newId('user') }, payload: {} }).valid, 'newId(event) builds a valid envelope');