MediaJobProposedPayload media.job.proposed@1

Generated at from openvibe-contracts v0.33.0 and openvibe-sdk v0.5.0.

Version
1.0.0
Owner
media
Visibility
first-party
Status
active
Compatibility
backward
Decision
ADR-004
Schema
https://openvibe.network/contracts/events/payloads/media.job.proposed.v1.json

media.job.proposed v1 (OpenVibe.Media server/jobs/queue.js enqueue; server/events.js recordJob). A job was proposed and waits for its owner: approving it queues it (media.job.queued), cancelling it ends it (media.job.cancelled); the worker never runs a proposal. The size-invariant validator (invariant.scan) proposes object.split and object.remux this way. Emitted in the SQLite transaction that records the state change, so the event exists if and only if the change committed; it goes to OpenVibe.Events only (no app webhook). The payload is an event projection of the job (queue.jobEvent): its identity, state, counters, stable error_code, times and has_result. Deliberately left out, because events travel beyond the tenant: the tenant's params (object.split keeps whatever the caller sent), the caller's idempotency_key, the free-text error (raw handler or ffmpeg output), the result (thumbnail.regenerate's is the thumbnail URL, of a private VOD too), created_by/decided_by and owner_user_id (tenant-local user ids), and the handler's checkpoint, lease and request hash. GET the job with a tenant token for params/result/error (GET /api/v2/:app/jobs/:id with a token for app_id; has_result says whether there is a result to fetch). Times are ISO 8601 UTC (YYYY-MM-DDTHH:MM:SS.sssZ) or null. Jobs of developer-project sandbox tenants are not announced. Envelope: subject { type: job, id: <id> }, visibility internal, priority important, actor service:media.

Fields

FieldTypeRequiredDescriptionConstraints
idstringyesThe job. GET /api/v2/:app/jobs/:id with a token for app_id answers the rest of it.
  • pattern ^mjob_[0-9A-HJKMNP-TV-Z]{26}$
app_idstringyesThe tenant that owns the job: an app (live, tools…) or a developer project's production tenant (prj_…).
  • minLength 1
  • maxLength 200
object_idstring | nullyesThe object the job works on; null for a tenant-wide job (invariant.scan).
  • maxLength 200
typestringyesJob type: thumbnail.regenerate, invariant.scan, object.split or object.remux.
  • pattern ^[a-z][a-z0-9]*\.[a-z0-9_.]+$
  • maxLength 80
statusconstyes
  • = "proposed"
attemptsintegeryesAttempts started so far.
  • minimum 0
max_attemptsintegeryes
  • minimum 1
  • maximum 10
error_codenullyes
cancel_requestedbooleanyesIts owner asked a running job to stop.
has_resultconstyesA proposal has no result.
  • = false
run_afterstring | nullyesA queued job waits until then (retry backoff); null otherwise.
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
decided_atnullyesNobody has decided yet.
created_atstringyes
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
updated_atstringyes
  • pattern ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,3})?Z$
  • format date-time
started_atnullyes
finished_atnullyes

Examples

From the contract's own test fixtures: valid ones validate, rejected ones must fail.

Valid: invariant-split
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "proposed",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:02:11.000Z",
  "started_at": null,
  "finished_at": null
}
Valid: remux-for-a-project
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "prj_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.remux",
  "status": "proposed",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:02:11.000Z",
  "started_at": null,
  "finished_at": null
}
Rejected: already-started
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "proposed",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:02:11.000Z",
  "started_at": "2026-09-23T18:02:12.000Z",
  "finished_at": null
}
Rejected: carries-creator
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "proposed",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:02:11.000Z",
  "started_at": null,
  "finished_at": null,
  "created_by": "system:invariant.scan",
  "idempotency_key": "invariant:med_01JAB2C3D4E5F6G7H8J9K0MNPQ:object.split"
}
Rejected: carries-params
{
  "id": "mjob_01JAB3C4D5E6F7G8H9J0K1MNPQ",
  "app_id": "live",
  "object_id": "med_01JAB2C3D4E5F6G7H8J9K0MNPQ",
  "type": "object.split",
  "status": "proposed",
  "attempts": 0,
  "max_attempts": 3,
  "error_code": null,
  "cancel_requested": false,
  "has_result": false,
  "run_after": null,
  "decided_at": null,
  "created_at": "2026-09-23T18:02:11.000Z",
  "updated_at": "2026-09-23T18:02:11.000Z",
  "started_at": null,
  "finished_at": null,
  "params": {
    "source_size_bytes": 9663676416,
    "target_bytes": 2147483648,
    "reason": "public_object_too_large",
    "parts": 5
  }
}

Validate

const contracts = require('openvibe-contracts');
contracts.validate('media.job.proposed@1', value);   // { valid, errors: [{ path, message }] }