# Assets and versioning

> Asset identity, storage locators, the five typed version relations, lifecycle states, and how composition and external identifiers ride along.

An asset in OWG is a typed, versioned, governed object that carries a **locator** to wherever the bytes physically live. The asset node holds governance; the storage system holds content. That separation is what lets the same governance record survive a migration between clouds.

```json
{
  "id": "delivery_mp4",
  "role": "delivery",
  "type": "video.delivery",
  "content_type": "video/mp4",
  "version_relation": "representation",
  "predecessor": "source_mov",
  "produced_by": "transcode",
  "state": "delivered",
  "storage": {
    "provider": "s3",
    "locator": "s3://studio-media/out/clip.mp4",
    "zone": "us-east-2"
  }
}
```

Only `id` is required.

## Fields

| Field | Type | Notes |
|---|---|---|
| `id` | string | **Required.** Document-local identifier |
| `identity` | string | Global content-derived identity — content hash or credential manifest reference |
| `type` | string | Asset kind. Open set: `video`, `audio`, `image`, `text`, `model`, `lora`, `document` |
| `role` | string | Its function in this workflow: `source`, `delivery`, `thumbnail`, `captions` |
| `content_type` | string | MIME type |
| `storage` | object | Provider, locator, and optional zone or timerange |
| `produced_by` | string | Task id — derived; the task's `produced[]` is authoritative |
| `predecessor` | string | The asset this one came from |
| `version_relation` | enum | How it relates to its predecessor. See below |
| `state` | enum | Lifecycle position. See below |
| `assurance` | enum | `attested` \| `asserted` \| `inferred`. See below |
| `credentials` | object | e.g. `{ "scheme": "c2pa" }` |
| `identifiers` | array | External identifier pass-through |
| `depicts` | — | What the asset shows |
| `depicts_real_entity` | — | Deepfake trigger. See [AI disclosure](/compliance) |
| `composes` | array | Composition arcs. See below |
| `profiles` | object | Profile-contributed extensions, keyed by profile id (v0.92). See [Extensions](/specification#extensions) |

**`id` versus `identity`.** `id` is local to the document and is what tasks reference. `identity` is the global, content-derived identity — a hash, URN, or content-credential manifest reference — and it is what lets the same asset be recognised across many runs, systems, and organizations. Populate both.

## Typed version relations

This is one of the most consequential details in the specification. The relationship between versions is **typed, not merely numbered**.

| Relation | Meaning | Example |
|---|---|---|
| `revision` | Incremental change, same context | Rejected comp re-rendered after notes |
| `variant` | Change for a different context | Pristine versus battle-damaged prop |
| `derivation` | A new thing that acknowledges its source | A model trained from a base checkpoint |
| `representation` | Pure format change, same meaning | The proxy of a master |
| `alternative` | One pick from equivalent options | Concept sketches to choose between |

Correctly labelling the relation is the difference between semantically correct lineage and a version counter that quietly lies. Two rules are worth memorising:

- **A proxy is not a `revision` of the master.** It is a `representation` — same meaning, different format.
- **An approved retake is a `revision`, not a new asset.**

Get these wrong and every downstream question — *which shots use the latest approved version?*, *what is this derived from?* — returns a confidently incorrect answer. A numbered version chain cannot distinguish "this replaces that" from "this is another way of expressing that", and those have opposite implications for approval.

```json
{
  "id": "hero_prop_battle_damaged",
  "version_relation": "variant",
  "predecessor": "hero_prop_pristine"
}
```

```json
{
  "id": "master_proxy_h264",
  "version_relation": "representation",
  "predecessor": "master_prores"
}
```

## Lifecycle states

| State | Meaning |
|---|---|
| `draft` | Work in progress |
| `growing` | Still being appended to — a live feed. See below |
| `in_review` | Submitted, awaiting a decision |
| `accepted` | Approved |
| `delivered` | Handed over |
| `superseded` | Replaced by a newer version |
| `rejected` | Not approved — and **retained** in the graph |

A rejected asset stays in the graph as a rejected state rather than disappearing. Nothing is overwritten, which is what makes the audit trail a record rather than a reconstruction.

### Growing assets

`growing` is how OWG handles media that is still being captured, without mirroring a single byte:

- While an asset is `growing`, its `identity` is the **source UUID** from `identifiers[]` rather than a content hash — you cannot hash a file that is still being written.
- The producing ingest task **stays open** for the duration.
- When the referenced range closes, fixity is computed over the now-bounded range and the state advances.

This is what carries the model into live sports and news. See the [TAMS binding](/standards-bindings#bbc-tams-growing-assets-and-opaque-timeranges).

## Storage locators

```json
{
  "storage": {
    "provider": "tams",
    "timerange": "[1500:0_1620:0)"
  }
}
```

`provider` is an open string. Commonly used values — an implementation may register its own:

| Provider | Meaning |
|---|---|
| `s3`, `gcs` | Object storage |
| `saas_dam`, `iconik`, `frameio` | SaaS asset managers |
| `camera_media_card` | Media not yet ingested |
| `tams` | Time-addressable media store |
| `usd_resolver` | An OpenUSD logical asset identifier |
| `protected_store` | A hardened or protective storage tier |

> **`timerange` is stored opaquely.** OWG does not parse or interpret time. The half-open `[s:ns_s:ns)` syntax belongs to TAMS, and resolving it to bytes is TAMS's job. OWG deliberately does not attempt to solve the International Atomic Time to SMPTE timecode mapping.

## External identifiers

OWG never strips an incoming identifier, even one it does not use itself.

```json
{
  "identifiers": [
    { "scope": "tams.source",   "id": "0f7e...-a91c" },
    { "scope": "usd.assetInfo", "id": "asset://hero_prop/v23" },
    { "scope": "frameio.id",    "id": "b2c8e9f1" },
    { "scope": "c2pa",          "id": "urn:uuid:9e21..." }
  ]
}
```

Both `scope` and `id` are required per entry. Documented scopes: `tams.source`, `usd.assetInfo`, `frameio.id`, `c2pa`, `urn`. Preserved identifiers are the highest-confidence signal for relating objects that arrive from different systems — see [declared assurance](/standards-bindings#2-declared-assurance-on-every-cross-system-edge).

## Composition

`composes[]` records that an asset is assembled from other assets, using the arc vocabulary of the composition system it came from.

```json
{
  "id": "shot_0140_assembly",
  "composes": [
    { "component": "hero_prop_v23", "arc_type": "reference",
      "prim_path": "/World/Set/Hero", "variant_selection": "lod=high" },
    { "component": "env_backlot_v4", "arc_type": "payload",
      "prim_path": "/World/Env" }
  ]
}
```

Arc types: `reference`, `payload`, `sublayer`, `inherit`, `specialize`, `variant`. Only `component` is required.

**The graph never descends below the published-asset boundary.** `prim_path` and `variant_selection` are opaque edge properties — OWG stores them and does not interpret them. That boundary is what keeps a governance graph from turning into a scene graph.

## Assurance and credentials

```json
{
  "assurance": "attested",
  "credentials": { "scheme": "c2pa" }
}
```

Assurance states how strongly an asset's identity is backed. It uses the same three levels, and the same rule about confidence, as [relationships](/relationships#the-three-assurance-levels):

| Level | Meaning | Requires `confidence` + `method` |
|---|---|---|
| `attested` | A cryptographically verified content credential backs this | No |
| `asserted` | Declared by a participant or system, without cryptographic attestation | No |
| `inferred` | Identity established by a matcher rather than declared | **Yes** |

An inferred identity carries its score and method, exactly as a proposed edge does:

```json
{
  "id": "recovered_plate_0140",
  "identity": "sha256:9f2c…",
  "assurance": "inferred",
  "confidence": 0.79,
  "method": "m-corroborated-2"
}
```

The schema enforces the pairing in both directions: `inferred` without `confidence` and `method` is a validation error, and `attested` or `asserted` **with** them is also an error — a confidence score on a human assertion is meaningless.

Offline and physical steps default to `asserted`: a manually logged camera card is an assertion, whereas signed device output is an attestation. The ingest station — where physical provenance becomes digital provenance — is the boundary where one can become the other.

Note that the C2PA declaration lives on the **asset**, not on the task that produced it.
