LineageResolution lineage.resolution@1

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

Version
1.0.0
Owner
live
Visibility
first-party
Status
active
Compatibility
backward
Schema
https://openvibe.network/contracts/lineage/resolution.v1.json

The canonical channel/owner resolver's answer to lineage.resolve-request@1 (roadmap §15.10, requirement D20; capability live.lineage.resolve). One resolver serves channel, stream, VOD, clip, Pulse and creator-UI callers. PRECEDENCE between inputs (the roadmap's order): 1 explicit_slug (slug), 2 nested_slug (slug <channel>/<slot>, or parent_slug), 3 channel_id, 4 stream_lookup (stream_id, then slot_id), 5 the record's own lineage (clip_id, then vod_id), 6 media_lineage (media_object_id), 7 owner_subject, 8 legacy_map (legacy_ids). The first input in that order that resolves decides (resolved_by). Every other input that resolves must name the same channel, otherwise the answer is unresolved with reason conflict. An input whose source could not be asked makes the answer unresolved with reason source_unavailable, since it might have disagreed. An input that names nothing is reported not_found in checked and does not block the others. LINEAGE inside a record, first link that answers wins: a stream belongs to its owner, and a slot too; a VOD goes to its stream, then to its slot when the stream row is gone, then to the owner recorded on the VOD (legacy_metadata); a clip goes to its stream, then to its parent VOD's lineage (vod_parent), then to the channel recorded on the clip (legacy_metadata), and never to the clipper; a Media object goes to the record its legacy_ref names (media_lineage), then to its owner subject, then to the owner user recorded on it (legacy_metadata); a legacy:<app>:<kind>:<id> reference is read as that record (legacy_map). CONFIDENCE is the weakest hop: exact when the input itself names the channel, a stream or slot row, or the owner subject; derived when the answer came through a parent record or an owner recorded on a record; legacy_map when it needed a legacy id map (legacy_ids, a legacy media reference). A display name never resolves anything. Unresolved answers carry no channel.

Fields

FieldTypeRequiredDescriptionConstraints
statusenumyes
  • one of "resolved", "unresolved"
reasonenumWhy nothing resolved. no_input: the request named nothing. display_name_only: only a display name was given. not_found: no input names anything that exists. conflict: two inputs name different channels. ambiguous: an input names more than one channel (an owner subject linked to two accounts). source_unavailable: a record could not be read (Media down); retry later.
  • one of "no_input", "display_name_only", "not_found", "conflict", "ambiguous", "source_unavailable"
detailstring
  • maxLength 500
channelobject
  • no other fields
channel.idstringyesThe channel's id at its owner (Live: channels.id).
  • pattern ^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$
channel.slugstringyesThe channel's current slug (Live: the username, /@<slug>). Slugs can change; id and owner_subject do not.
  • pattern ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,63}$
channel.owner_subjectstring | nullyesThe owner's canonical subject, or null while the owner has not signed in through OpenVibe.Network since subjects were issued.
  • pattern ^usr_[0-9A-HJKMNP-TV-Z]{26}$
channel.legacy_idsobjectThe owner's pre-subject ids, for callers that still key by them.
  • no other fields
channel.legacy_ids.live_user_idinteger
  • minimum 1
channel.legacy_ids.network_user_idinteger
  • minimum 1
streamobject
  • no other fields
stream.idstring | nullyesThe broadcast session, or null when only a slot was named.
stream.slot_idstring | null
stream.slot_slugstring | null
stream.missingbooleantrue when a record names this stream but the stream row no longer exists.
vodobject
  • no other fields
vod.idstringyes
vod.stream_idstring | null
vod.slot_idstring | null
clipobject
  • no other fields
clip.idstringyes
clip.vod_idstring | null
clip.stream_idstring | null
media_objectobject
  • no other fields
media_object.idstringyes
  • pattern ^(med_[0-9A-HJKMNP-TV-Z]{26}|legacy:[a-z][a-z0-9-]{1,39}:(vod|clip|file|paste|thumbnail|avatar):[A-Za-z0-9._/-]{1,200})$
media_object.kindstring | null
media_object.legacy_refstring | null
resolved_byenumThe input that decided.
  • one of "slug", "parent_slug", "channel_id", "stream_id", "slot_id", "clip_id", "vod_id", "media_object_id", "owner_subject", "legacy_ids"
ruleenumThe lineage rule that answered for the deciding input.
  • one of "explicit_slug", "nested_slug", "channel_id", "stream_lookup", "vod_parent", "media_lineage", "owner_subject", "legacy_metadata", "legacy_map"
confidenceenum
  • one of "exact", "derived", "legacy_map"
viaarray of stringThe hops the deciding input took, e.g. ["clip:7", "vod:42", "slot:3", "user:17"].
  • maxItems 32
  • items: pattern ^[a-z_]+:[^\s]{1,200}$
checkedarray of objectWhat each input given came to.
  • maxItems 32
  • items: no other fields
checked[].inputenumyes
  • one of "slug", "parent_slug", "channel_id", "stream_id", "slot_id", "clip_id", "vod_id", "media_object_id", "owner_subject", "legacy_ids", "display_name"
checked[].outcomeenumyes
  • one of "decided", "agrees", "conflict", "not_found", "unavailable", "ambiguous", "ignored"
checked[].channel_slugstringThe channel this input named, when it named one.

Examples

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

Valid: clip-vod-stream-channel
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": null,
    "legacy_ids": {
      "live_user_id": 17
    }
  },
  "stream": {
    "id": "9",
    "slot_id": "3",
    "slot_slug": "garage-cam"
  },
  "vod": {
    "id": "42",
    "stream_id": "9",
    "slot_id": "3"
  },
  "clip": {
    "id": "7",
    "vod_id": "42",
    "stream_id": null
  },
  "resolved_by": "clip_id",
  "rule": "vod_parent",
  "confidence": "derived",
  "via": [
    "clip:7",
    "vod:42",
    "stream:9",
    "user:17"
  ],
  "checked": [
    {
      "input": "clip_id",
      "outcome": "decided",
      "channel_slug": "alice"
    }
  ]
}
Valid: deleted-stream-surviving-vod
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
  },
  "stream": {
    "id": "9",
    "slot_id": "3",
    "slot_slug": "garage-cam",
    "missing": true
  },
  "vod": {
    "id": "42",
    "stream_id": "9",
    "slot_id": "3"
  },
  "resolved_by": "vod_id",
  "rule": "stream_lookup",
  "confidence": "derived",
  "via": [
    "vod:42",
    "slot:3",
    "user:17"
  ]
}
Valid: legacy-media-ref
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": null
  },
  "vod": {
    "id": "42",
    "stream_id": "9",
    "slot_id": null
  },
  "media_object": {
    "id": "legacy:live:vod:42",
    "kind": "vod",
    "legacy_ref": "legacy:live:vod:42"
  },
  "resolved_by": "media_object_id",
  "rule": "legacy_map",
  "confidence": "legacy_map",
  "via": [
    "media:legacy:live:vod:42",
    "vod:42",
    "stream:9",
    "user:17"
  ]
}
Valid: slug
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
    "legacy_ids": {
      "live_user_id": 17,
      "network_user_id": 57
    }
  },
  "resolved_by": "slug",
  "rule": "explicit_slug",
  "confidence": "exact",
  "via": [
    "user:17"
  ],
  "checked": [
    {
      "input": "slug",
      "outcome": "decided",
      "channel_slug": "alice"
    }
  ]
}
Valid: unresolved-conflict
{
  "status": "unresolved",
  "reason": "conflict",
  "checked": [
    {
      "input": "slug",
      "outcome": "decided",
      "channel_slug": "mallory"
    },
    {
      "input": "vod_id",
      "outcome": "conflict",
      "channel_slug": "alice"
    }
  ]
}
Valid: unresolved-display-name
{
  "status": "unresolved",
  "reason": "display_name_only",
  "detail": "a display name is never sufficient to establish a channel or its owner",
  "checked": [
    {
      "input": "display_name",
      "outcome": "ignored"
    }
  ]
}
Rejected: local-owner-subject
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": "17"
  },
  "resolved_by": "slug",
  "rule": "explicit_slug",
  "confidence": "exact"
}
Rejected: resolved-without-channel
{
  "status": "resolved",
  "resolved_by": "slug",
  "rule": "explicit_slug",
  "confidence": "exact"
}
Rejected: unknown-confidence
{
  "status": "resolved",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": null
  },
  "resolved_by": "display_name",
  "rule": "explicit_slug",
  "confidence": "guessed"
}
Rejected: unresolved-with-channel
{
  "status": "unresolved",
  "reason": "conflict",
  "channel": {
    "id": "12",
    "slug": "alice",
    "owner_subject": null
  }
}
Rejected: unresolved-without-reason
{
  "status": "unresolved"
}

Validate

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