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

FieldTypeRequiredDescriptionConstraints
idstringyesMinted 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>`).
  • pattern ^job_[0-9A-HJKMNP-TV-Z]{26}$
classplatform.runtime-classyes
artifactobjectThe pre-registered artifact to run; required for classes `function` and `code`.
  • no other fields
artifact.namestringyes
  • pattern ^[a-z0-9][a-z0-9._-]{0,127}$
artifact.versionstringyesOne exact version, never a range
  • pattern ^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$
argsobjectyesThe artifact's declared input as JSON; the whole `job` frame stays within the link's 1 MiB frame limit.
ttl_msintegeryesLifetime 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`.
  • minimum 1
limitsobjectyesCaps the worker enforces on the running process (merged stricter with its local caps); exceeding one kills the job and `job_exit` says `limit`.
  • no other fields
limits.wall_msintegeryesRunning time from `job_started`
  • minimum 1
limits.cpu_msintegeryesCPU time, user plus system
  • minimum 1
limits.mem_bytesintegeryesPeak resident memory
  • minimum 1
netenumNetwork 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.
  • one of "deny", "none", "public", "openvibe-only"
  • default "deny"
inputsarray of objectFiles 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.
  • maxItems 32
  • items: no other fields
inputs[].namestringyes
  • pattern ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$
inputs[].media_idstringyes
  • pattern ^med_[0-9A-HJKMNP-TV-Z]{26}$
inputs[].sha256stringyes
  • pattern ^[0-9a-f]{64}$
inputs[].size_bytesinteger
  • minimum 0

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 }] }