Skip to content
VERASPEC
Repository
VER 1.1 — draftdraft

§3 Lineage (ADR-0005)

3.1 The problem

VER 1.0 records what a pipeline did to an Asset (provenance.chain) and what a signer asserted about it (C2PA), but nothing about where the Asset came from when it was produced from other Assets. Crops, composites, upscales, and exports are the overwhelming majority of images in circulation, and a Record for a composite currently claims a custody chain that begins at import with no statement that two other Assets are inside it.

3.2 Decision

Options considered: (a) an in-Record lineage object; (b) separate lineage and recipe Records; (c) both. The draft selects (c): minimal parentage in the Record, rich recipes out of the Record and bound by hash. (a) alone forces unbounded recipe documents into every Record; (b) alone makes the common case — “this came from those two” — require a second fetch.

ADR-0005 is accepted in model, revised in shape. The revision is the parent reference: an earlier revision of this draft let a parent be identified by a bare record_id, which annex E16 makes producer-scoped and which therefore resolves nowhere, or by a pixel_hash alone, which annex E27 says is a grouping key and not an identity. Both are now excluded from the identifier rule.

3.3 Normative text (DRAFT)

A Record MAY carry lineage:

json
"lineage": {
"parents": [
{ "record_uri": "https://dam.example.net/ver/records/3fedc64a-…",
"producer_id": "did:web:dam.example.net",
"record_id": "3fedc64a-d12f-562d-9abd-473a559f837d",
"content_hash": "47551af5…",
"pixel_hash": { "value": "c9d62902…", "cpnp_version": "1.0" },
"relation": "composed",
"note": "left panel" }
],
"recipe_sha256": "7d8485b7…",
"recipe_uri": "https://recipes.example.org/compose/7d8485b7….json"
}
  • parents is non-empty. Each entry MUST carry at least one **globally resolvable** identifier: either record_uri, or the pair {producer_id, record_id}. This is a rule about resolvability — being able to obtain a statement about the parent — not about identity strength.
  • A bare record_id does not satisfy that rule. E16 scopes record_id to its producer, so on its own it names nothing outside that producer's namespace. It MAY accompany a producer_id (which is what makes the pair resolvable) or a record_uri.
  • **content_hash SHOULD be present whenever the Producer holds the parent bytes.** It is the only parent identifier stable across producers (E27), and it is what lets a Consumer confirm that the thing it resolved is the thing the Producer meant. It does not satisfy the resolvability rule on its own, because a digest says what the parent is and not where a statement about it can be found. A parent reference carrying no content_hash SHOULD draw a conformance warning, and MUST be an error in a named high-trust profile (§3.5).
  • **pixel_hash is an object, {value, cpnp_version}, and never satisfies the resolvability rule.** A parent identified down to a pixel_hash resolves to a candidate, not to an identity: a Consumer matching such a reference against its own holdings MUST treat every match as a candidate parent, MUST NOT treat a match as proof that it holds the parent, and MUST NOT use the match as a join key. pixel_hash is rendered-identity grouping (E27): distinct Assets that render to one Canonical Buffer share it by design, which is the property that makes it useful and the property that makes it wrong as a primary key. The cpnp_version member is why the object form exists — a digest without its universe is a digest a Consumer will compare across universes (1.0 §13, E10).
  • Absence of lineage means “not stated”, never “no parents”. A Consumer MUST NOT infer from a missing lineage block that the Asset has no parents. Stated explicitly because the opposite reading is the natural one and it is wrong: most Records will omit lineage because their Producer does not track it, not because the Asset is original.
  • relation is one of:
    • derived — one parent, transformed (crop, resize, upscale, colour adjustment, denoise, or any re-encode that changes the Canonical Buffer).
    • composed — this Asset contains pixel content from this parent alongside others. Multi-parent lineage uses composed for every contributing parent.
    • exported — this Asset is a re-encoding of the parent with **no change to the Canonical Buffer**: parent and child share a pixel_hash and differ only in content_hash. A rendition that resamples, crops, or re-grades is derived, not exported; so is a re-encode that drops render-affecting metadata, because removing a consumed ICC profile or Orientation tag changes the Canonical Buffer (E1).
  • recipe_sha256, when present, is the SHA-256 of the RFC 8785 JCS serialization of the recipe envelope (§3.4). **recipe_uri present ⇒ recipe_sha256 REQUIRED** (schema-encoded, §15.3); a Consumer that fetches the URI MUST verify the document against the digest before use.
  • A Record carrying a compose chain event MUST carry lineage with at least two composed parents (schema-encoded).
  • lineage is an assertion by the Producer, at trust tier unsigned unless the Record is signed. A Consumer MUST NOT treat lineage as provenance evidence in the C2PA sense; it is a pipeline claim, exactly like provenance.chain.
  • lineage MUST NOT contain a cycle. A Consumer that detects one — by following record_uri, {producer_id, record_id} or content_hash edges — MUST treat the affected Records as non-conformant.

Lineage depth is deliberately not bounded here; a parent's own lineage lives in the parent's Record. Deep graphs are traversed, not inlined.

3.4 The recipe envelope

recipe_sha256 binds a document whose format was previously unstated, which makes “verify it before use” unactionable: two producers cannot agree on what they are hashing. The envelope is minimal on purpose — VER does not specify image compositing — but it is specified.

A recipe document is served with media type application/json and is a JSON object with this top level:

json
{
"recipe_version": "1",
"canonicalization": "rfc8785-jcs",
"steps": [ { "op": "canvas", "width": 128, "height": 48, "background": "#000000" },
{ "op": "place", "source": "parent-a", "x": 0, "y": 0 },
{ "op": "place", "source": "parent-b", "x": 64, "y": 0 } ],
"x-org.example.studio": { "template": "diptych-128x48" }
}
  • recipe_version is REQUIRED and is the envelope's version, not the producer's pipeline version. "1" is the only value this draft defines.
  • canonicalization is REQUIRED and MUST be "rfc8785-jcs". It is stated in the document rather than assumed, so that a document whose digest is being checked says which serialization the digest covers.
  • steps is REQUIRED and is an array. VER does not define step vocabulary; op names an operation whose meaning belongs to the producing tool. A Consumer MUST NOT infer pixel semantics from a step it does not recognise.
  • Any other top-level member MUST be an x- authority keyx-<reverse-dns-authority> — and a Consumer MUST ignore keys it does not recognise. Unprefixed members other than the three above are not permitted: the envelope has to stay recognisable as an envelope.
  • recipe_sha256 is SHA-256 over the RFC 8785 JCS serialization of this object. The digest is a commitment, not a disclosure requirement: a proprietary recipe can be committed to and released later, or never.

Publishing this envelope as its own schema artifact (ver-recipe.schema.json) is a ratification deliverable, listed on RELEASE.md.

3.5 Semantic fixtures required before ratification

Lineage is where a schema check is least useful: every interesting failure is a statement about a graph, not about a document. Eight fixtures MUST exist and be exercised before §3 ratifies, and each names the behaviour it pins:

#FixtureWhat it must show
1exportedParent and child share pixel_hash, differ in content_hash; accepted
2composedcompose chain event with two composed parents; accepted
3duplicate parentThe same parent listed twice; rejected or warned, and the ruling stated
4contradictoryrelation: "exported" with parent and child pixel_hash values that differ; rejected
5self-parentA parent reference resolving to the Record itself; rejected
6cycleA → B → A across two Records; rejected, with the detection rule exercised on each edge type
7missing digestrecipe_uri with no recipe_sha256; rejected by the schema
8danglingA record_uri that does not resolve; accepted — an unresolvable parent is not a malformed Record, and a validator that made network reachability part of conformance would repeat VER1303's mistake

The named high-trust profile referenced in §3.3 is also a ratification deliverable: a profile identifier, a published rule list, and the promotion of the content_hash-absent warning to an error within it.