Job platform.job@1
Generated at from
openvibe-contracts v0.114.0 and
openvibe-sdk v0.35.1.
- Version
- 1.0.0
- Owner
- network
- Visibility
- public
- Status
- active
- Compatibility
- backward
- Decision
- ADR-034
- Schema
https://openvibe.network/contracts/platform/job.v1.json
platform.job@1: one unit of work a dispatcher (OpenVibe.Bot now, Fabric later) hands to an execution worker (plan T14 Run; ADR-034 proposed). It rides the device control link inside a `job` frame (platform.job-frame@1). The worker answers it with `ack` (accepted) or `nack` (refused: an unknown or unimplemented class, a missing artifact, limits it cannot honor, the local kill switch), then runs it at most once. Metering: a running `function` job is billed by wall-clock second as platform.usage-sample@1 readings; platform.job-frame@1 `job_usage` gives the mapping.
Fields
| Field | Type | Required | Description | Constraints |
|---|---|---|---|---|
id | string | yes | Minted by the dispatcher (job_<ULID>): the idempotency and ack key, as a Node command `id` is. A worker that already holds or ran this id never runs it again: a resent `job` is acked again while it runs and answered with its `job_exit` once it ended. Never reused: it is the stem of every usage reading's key (`run:<id>:<second>`). |
|
class | platform.runtime-class | yes | ||
artifact | object | The pre-registered artifact to run; required for classes `function` and `code`. |
| |
artifact.name | string | yes |
| |
artifact.version | string | yes | One exact version, never a range |
|
args | object | yes | The artifact's declared input as JSON; the whole `job` frame stays within the link's 1 MiB frame limit. | |
ttl_ms | integer | yes | Lifetime from the worker's receipt of the job, covering any wait and the run; the worker caps it with its own local maximum (the stricter wins, as with `max_command_ms`). When it runs out, a job not yet started never starts and a running one is killed; either way `job_exit` says `ttl`. |
|
limits | object | yes | Caps the worker enforces on the running process (merged stricter with its local caps); exceeding one kills the job and `job_exit` says `limit`. |
|
limits.wall_ms | integer | yes | Running time from `job_started` |
|
limits.cpu_ms | integer | yes | CPU time, user plus system |
|
limits.mem_bytes | integer | yes | Peak resident memory |
|
net | enum | Network policy; absent means `deny`. `none` is `deny` spelled as OpenVibe.Node's worker.egress names it; `public` and `openvibe-only` ask for egress the host must already allow (ADR-046): a worker whose host policy is stricter runs the stricter one, and a worker that cannot enforce what is asked refuses the job (`nack`) rather than running it weaker. |
| |
inputs | array of object | Files placed in the job's working directory before the process starts. Each is an OpenVibe.Media object pinned by digest: a size or digest mismatch ends the job failed before the process ever runs (run.job.input_digest_mismatch). Never a caller-chosen URL and never a credential; the worker holds its own token. Names are unique and carry no path separator. |
| |
inputs[].name | string | yes |
| |
inputs[].media_id | string | yes |
| |
inputs[].sha256 | string | yes |
| |
inputs[].size_bytes | integer |
|
Examples
From the contract's own test fixtures: valid ones validate, rejected ones must fail.
Valid: code-with-inputs
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
"class": "code",
"artifact": {
"name": "transcode",
"version": "0.4.1"
},
"args": {
"format": "webm"
},
"ttl_ms": 120000,
"limits": {
"wall_ms": 90000,
"cpu_ms": 90000,
"mem_bytes": 536870912
},
"net": "public",
"inputs": [
{
"name": "clip.mp4",
"media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"sha256": "9f86d081884c7d659a2feb15b0b4f8f1c0e6ad1d7e6fd2e1b0b6fd1c4f1d2a3b",
"size_bytes": 1048576
}
]
}Valid: function
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "1.2.0"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "deny"
}Valid: net-absent-means-deny
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "1.2.0"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
}
}Valid: reserved-class-without-artifact
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "gpu",
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "deny"
}Rejected: artifact-version-range
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "^1.2"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "deny"
}Rejected: code-without-artifact
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
"class": "code",
"args": {
"format": "webm"
},
"ttl_ms": 120000,
"limits": {
"wall_ms": 90000,
"cpu_ms": 90000,
"mem_bytes": 536870912
},
"net": "public",
"inputs": [
{
"name": "clip.mp4",
"media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"sha256": "9f86d081884c7d659a2feb15b0b4f8f1c0e6ad1d7e6fd2e1b0b6fd1c4f1d2a3b",
"size_bytes": 1048576
}
]
}Rejected: function-without-artifact
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "deny"
}Rejected: id-without-prefix
{
"id": "01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "1.2.0"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "deny"
}Rejected: input-bad-digest
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
"class": "code",
"artifact": {
"name": "transcode",
"version": "0.4.1"
},
"args": {
"format": "webm"
},
"ttl_ms": 120000,
"limits": {
"wall_ms": 90000,
"cpu_ms": 90000,
"mem_bytes": 536870912
},
"net": "public",
"inputs": [
{
"name": "clip.mp4",
"media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"sha256": "f86d081884c7d659a2feb15b0b4f8f1c0e6ad1d7e6fd2e1b0b6fd1c4f1d2a3b",
"size_bytes": 1048576
}
]
}Rejected: limits-without-mem
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "1.2.0"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000
},
"net": "deny"
}Rejected: net-allow
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"class": "function",
"artifact": {
"name": "thumbnail",
"version": "1.2.0"
},
"args": {
"width": 320
},
"ttl_ms": 60000,
"limits": {
"wall_ms": 30000,
"cpu_ms": 30000,
"mem_bytes": 268435456
},
"net": "allow"
}Rejected: unknown-net
{
"id": "job_01JAB2C3D4E5F6G7H8J9K0MNPR",
"class": "code",
"artifact": {
"name": "transcode",
"version": "0.4.1"
},
"args": {
"format": "webm"
},
"ttl_ms": 120000,
"limits": {
"wall_ms": 90000,
"cpu_ms": 90000,
"mem_bytes": 536870912
},
"net": "restricted",
"inputs": [
{
"name": "clip.mp4",
"media_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
"sha256": "9f86d081884c7d659a2feb15b0b4f8f1c0e6ad1d7e6fd2e1b0b6fd1c4f1d2a3b",
"size_bytes": 1048576
}
]
}Validate
const contracts = require('openvibe-contracts');
contracts.validate('platform.job@1', value); // { valid, errors: [{ path, message }] }