WAI Extension: Direction Records
Mirrored from the canonical text at commit b1681667 ().
Status: Draft. Registers the record
wai.media.direction: a person’s direction of generated or edited video, as signed evidence bound to the output. A director, a person named by an Ed25519 key (as adid:key, the same key), signs direction records at four levels of control: a storyboard fixes the shots, each with its duration, its subjects by reference picture and its framing (the shot level), and may fix their order (the sequence level); keyframes fix, at stated times of a shot, a subject’s box on screen, its size class, its screen position and its angle, relative to the subject, and a camera window, with a pinned interpolation between keys (the frame level), and regions hold samples of stated frames to a reference picture (the pixel level); a sequence fixes the edit: which shots, which part of each, in which order, cut or dissolved (the sequence level). Each record states the levels it sets. A tool that makes a video under direction signs a directed output record naming the direction records, the layout of every frame of every shot, the timeline of the edit and the output’s frames by digest. Where the output was made by the pinned render of this extension from layers thatwai.generate.spectralregenerates or that the verifier holds, every frame recomputes, and the checks are exact: each cut falls at its directed frame, each duration and the order are the directed ones, each subject is where the keyframes and their interpolation put it under a pinned integer framing measure, and each held region holds. Where a model made the frames in floating point, the same checks run on the layout the tool states, and the outcome is attested with the class of what stated it, never exact. Every directed element governs the output frames that show what it directed, and the record states them: the per-frame map shows which person directed each frame’s framing, timing, held samples and place in the sequence, whoever made its samples, and each element maps onto adirectstep of the contribution record and onto a C2PA action. WAI decides no legal question. No new transport. The reference implementation is thedirectionmodule ofwai-rs(featuredirection); the corpus islive-conformance/direction/, and its second implementation islive-conformance/direction_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.Texts read for this revision, each pinned to the version named: the C2PA Technical Specification, version 2.4, §6.4, §18.2 (§18.2.2.1, §18.2.2.4 and §18.2.3 among it), §18.15.1, §18.15.2, §18.15.4.1 and §18.15.4.6; the Creator Assertions Working Group’s Identity Assertion, version 1.2, §5.1, §5.1.2 and §6.1; the serialized schemas of OpenTimelineIO release 0.18.1 (
Timeline.1,Stack.1,Track.1,Clip.2,Transition.1,RationalTime.1,TimeRange.1) and its transition types; the abstract of arXiv 2606.01900; and Copyright and Artificial Intelligence, Part 2: Copyrightability, a report of the United States Copyright Office (January 2025), its Part II.F and its Part V. A section number of one of those texts in this extension is that version’s.
1. Scope
1.1 Why
Direction of generated video works at four levels: the pixel, the frame, the shot and the sequence. A director decides what each shot shows and how it is framed, where a subject sits on screen and how large, how the camera moves, which samples stay fixed, where each cut falls and in which order the shots run. arXiv 2606.01900 states framing as functions of the subject’s pose and motion (shot size, angle and composition) and directs a virtual camera with sparse keyframes deterministically interpolated into a trajectory. A generator then makes the samples.
The cited report concludes that outputs made with generative AI may be protected in whole or in part where a person was able to determine the expressive elements they contain, that prompts alone are unlikely to suffice at this stage, and that the questions are decided case by case (its Part V); and that a person may select, coordinate and arrange generated material, or modify it, in a way that contributes authorship (its Part II.F). Whether a director’s choices are such a contribution is a question for a registration authority or a court, under its own rule.
This extension makes the director’s choices signed facts, bound to the output they governed, and checks them: exactly, frame by frame, where the output recomputes; as the tool’s statement, with its class, where it does not. It states, for every frame, who directed what. It does not say what any of that makes anyone.
1.2 In scope and out of scope
In scope: the records and their encodings (§2); the levels and the elements of direction (§3);
the storyboard (§4); keyframes, their interpolation and held regions (§5); the sequence and the
timeline arithmetic (§6); the framing measure (§7); the directed output (§8); the render (§9);
governance, the per-frame map and the contribution record’s direct act (§10); the mappings
onto C2PA actions, identity assertions and an open timeline interchange format (§11);
verification (§12).
Out of scope:
- Legal questions. WAI decides none. A record states which person directed which frames at which level, and how much of that a verifier checked; a reader applies its own rule to those facts. Registering a work, and contacting any authority about it, are its owner’s decisions.
- How a generator follows direction. A model conditioned on a storyboard, a keyframe or a pose is the tool’s; this extension checks what came out.
- Measuring a subject in arbitrary pixels. Where the subject is a known layer, the render of §9 places it and its box is exact. A detector or tracker that finds a subject in a generated frame is a method of the tool’s, and what it states is attested with its class (§8.1).
- Audio and its direction, and direction of a live room’s composition, which live-compose lays out from the room’s events.
- Camera paths in world space. The camera of this revision is a window over a plate (§5.1); a path through a scene is a later revision’s.
1.3 Relationships
Relationship to the core spec: wai.media.direction is a record (SPEC §5):
signed records a WAI tool emits and verifies. It names no payload format and is not
dispatchable.
Relationship to wai.generate.spectral: a layer of a directed output may be a picture that
generator makes; a verifier holding the model file regenerates it (SPEC §5, Payload of
wai.generate.spectral).
Relationship to deriving-edge: the render of §9 places layers as that
extension’s wai.derive.overlay_composition does, opaquely at integer offsets, and resamples by
the nearest sample, the reference rule of its wai.derive.scaling (§4.2 there), which this
extension pins for its own render.
Relationship to synthesis: a directed output’s origin claim is that extension’s; direction changes no class. A video whose layers or frames a model generated is synthesized, however closely a person directed it.
Relationship to the contribution record, wai.media.contribution: that record reserves the act
direct for this extension. §10.3 defines the step, its parameters and its dependency rule.
Relationship to the exploration record, wai.media.exploration: a subject’s view, a shot’s
reference or a plate may be a picture an exploration archive holds, chosen by a pick; this
extension names it by its samples digest, and the archive and the pick are that record’s.
2. Records
Every record of this extension is a JSON object (RFC 8259) under the encodings of
jwp-receipts §2.4.3, read as synthesis §2 reads a record: strict JSON, no
member named twice, at most 32 levels of nesting, byte strings in lowercase hexadecimal,
integers unsigned and at most 2^53 − 1, no other number, no boolean, terms and
date-times as defined there. Each record has exactly the members its section lists, no
member missing and none added, at any level, and a kind naming it; one that does not is
record_malformed. So is a member outside the values or ranges its section gives, or one that
breaks a rule its section states of the record alone.
Every record carries signer_pubkey (32 bytes, an Ed25519 key of RFC 8032) and sig (64
bytes):
sig = Ed25519(signer_key, "wai:direction\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:direction-link\x01" || JCS(record))
JCS is RFC 8785. The signature is verified strictly (SPEC §7 Signatures): a key
that fails step 1 or 2 is bad_key, and a signature that fails a later step bad_signature. A
record is named by its link hash wherever another names it. issued_at holds the instant its
signer signed it, self-asserted.
Values used throughout:
- A signed integer is a string:
0, or an optional-followed by one to seven of the digits0–9of which the first is not0(no+, no space, no-0), with a value from −1 048 576 to 1 048 576. The encodings admit no negative number, and a subject’s box may begin left of or above the frame. - A box is
{"x", "y", "w", "h"}:xandysigned integers, the box’s left and top edge in pixels from the frame’s top-left corner;wandhintegers from 1 to 1 048 576. It may extend past any edge of the frame. - A rect is
{"x", "y", "w", "h"}, four integers,wandhat least 1: a rectangle of pixels within a picture,x+wat most its width andy+hat most its height, checked where the picture is known (§8.5, §12.2 step 2). - A picture is
width×heightpixels as theRGB8buffer of reconstruction-binding §2 (row-major, three bytes a pixel, no row padding), each side from 1 to 4096 andwidth·heightat most 2^23, the sizeswai.generate.spectralmakes. Its samples digest is SHA-256 (FIPS 180-4) over the buffer. A picture reference is{"samples", "width", "height"}: a samples digest and the size it is of. A pixel is addressed (x, y), x to the right and y down from the top-left. - A frame range is a pair
[a, b]of integers, a < b: the frames a to b − 1. A frame set is an array of frame ranges in ascending order, each beginning after the one before it ends and not where it ends (bof one belowaof the next): its one encoding. - ⌊a / b⌋, for b above 0, rounds toward negative infinity. Every quantity of this extension is an integer; none needs more than 127 bits with its sign.
3. Levels and elements
A level is one of pixel, frame, shot and sequence, in that order. Each direction
record has a member levels: the levels it sets, in that order, none twice. A record whose
levels is not exactly the levels its members set (§4, §5, §6) is record_malformed.
An element is one directed choice of a record, named by a path:
| level | element | path | what it fixes | its record |
|---|---|---|---|---|
shot | a shot | ["shot", i] | the shot’s duration, its subjects and the framing it opens on | storyboard (§4) |
frame | a shot’s keys | ["keyframes", e] | each subject’s box and classes, and the camera, at each key and between keys | keyframes (§5) |
pixel | a held region | ["region", j] | the samples inside rects of stated frames | keyframes (§5.3) |
sequence | the order and cuts | ["sequence"] | which shots in which order, which part of each, each cut and dissolve | storyboard (§4) or sequence (§6) |
i is a shot’s position in the storyboard, e an entry’s and j a region’s position in their
keyframes record. A record’s elements are, in this order: a storyboard’s shots by position,
then ["sequence"] where it sets that level; a keyframes record’s entries by position, then its
regions by position; a sequence record’s ["sequence"].
4. The storyboard
A director signs a storyboard, kind "wai-storyboard":
| member | type | meaning |
|---|---|---|
kind | string | "wai-storyboard" |
levels | array | ["shot"], or ["shot", "sequence"] when order is not null |
width, height | integer | every frame’s size: a picture’s sides (§2) |
rate | object | {"num", "den"}, each from 1 to 1 048 576: frames per second, as num / den |
subjects | array | 0 to 16 subjects (§4.1) |
shots | array | 1 to 256 shots (§4.2) |
order | array or null | 1 to 1024 shot positions, in the order the shots run, each a shot whole, joined by cuts; null where the storyboard does not fix an order |
issued_at | date-time | when the director signed it |
signer_pubkey, sig | §2: the director’s key |
4.1 Subjects
A subject is {"views"}: 1 to 8 views, each {"angle", "samples", "width", "height"},
a term naming the angle the subject is seen from and a picture reference: the subject’s
reference picture as seen from that angle, its whole extent edge to edge. No angle appears twice
in one subject. Angles are the director’s terms; front, three-quarter, profile, back,
high and low are examples, not a list. A subject is named by its position.
4.2 Shots
A shot is {"duration", "subjects", "framing", "reference"}:
duration: its number of frames, from 1 to 65 536;subjects: 0 to 16 subject positions, none twice, in drawing order, the first drawn first;framing: one entry per subject ofsubjects, in its order:{"size", "position", "angle"}, eachnullor a class:sizeandpositionthe classes of §7,anglean angle of that subject’s views. It is the framing the shot opens on: the measure of its first frame (§7);reference: a picture reference (§2), a reference picture for the shot, ornull.
A position in subjects or order that is not a subject’s or a shot’s, or an angle that is
not one of the subject’s views’, is record_malformed.
5. Keyframes
A director signs a keyframes record, kind "wai-keyframes":
| member | type | meaning |
|---|---|---|
kind | string | "wai-keyframes" |
storyboard | 32 B hex | the link hash of the storyboard it directs |
levels | array | ["frame"] when only keyframes is not null, ["pixel"] when only regions is not, ["pixel", "frame"] when neither is null |
keyframes | array or null | 1 to 256 entries, each {"shot", "keys"}: a shot’s position and 1 to 1024 keys (§5.1); no shot twice |
regions | array or null | 1 to 256 held regions (§5.3) |
issued_at | date-time | when the director signed it |
signer_pubkey, sig | §2: the director’s key |
A record whose keyframes and regions are both null is record_malformed.
5.1 Keys
A key is {"t", "interp", "camera", "subjects"}:
t: the frame of the shot it is at, from 0; the keys of an entry in strictly ascendingt;interp:hold,linearorease: how its values move to the next key’s (§5.2);camera:null, or a rect: the camera window, the part of the shot’s plate the frame shows, scaled to the frame (§9.1);subjects: one entry per subject of the shot, in the shot’s order, each{"box", "size", "position", "angle"}:boxnullor a box (§2), the subject’s whole extent on screen, actor relative: where the subject is and how large;size,positionandangleeachnullor a class (§7).
Within an entry, every key’s subjects has one length, and for each subject either every key
gives a box or none does; likewise the camera. Otherwise record_malformed.
A subject’s boxes, and the cameras, are tracks. A track begins at the entry’s first key.
The classes of a key are checked at its t alone.
5.2 Interpolation
A track’s value at frame t of the shot, from the first key’s t to the shot’s last frame, is
computed member by member (x, y, w, h): for t from a key k (at tₖ, value vₖ)
up to the next key (at tₖ₊₁, value vₖ₊₁), with n = t − tₖ and D = tₖ₊₁ −
tₖ, by key k’s interp:
interp | value |
|---|---|
hold | vₖ |
linear | vₖ + ⌊(vₖ₊₁ − vₖ) · n / D⌋ |
ease | vₖ + ⌊(vₖ₊₁ − vₖ) · n² · (3D − 2n) / D³⌋ |
From the last key to the shot’s end the value is the last key’s. ease is the cubic that
starts and ends with zero speed, s²(3 − 2s) of s = n / D, in integers. A value lies
between its two keys’ values, so w and h stay at least 1 and a camera window between two
windows inside the plate stays inside it.
5.3 Held regions
A held region is {"shot", "from", "to", "rects", "source"}: a shot’s position; its frames
from up to, not including, to (from < to); 1 to 64 rects within the frame; and a picture
reference of the frame’s size, the source. In those frames, every pixel inside a rect is the
source’s pixel at the same position. A region holds a reference picture, a drawn matte, a fixed
element or a locked background to exact samples, whatever made the rest of the frame.
6. The sequence
A director (an editor directs the sequence) signs a sequence, kind "wai-sequence":
| member | type | meaning |
|---|---|---|
kind | string | "wai-sequence" |
storyboard | 32 B hex | the link hash of the storyboard it edits |
levels | array | ["sequence"] |
events | array | 1 to 1024 events (§6.1) |
issued_at | date-time | when the director signed it |
signer_pubkey, sig | §2: the director’s key |
6.1 Events
An event is {"shot", "in", "out", "transition"}: a shot’s position; the frames of the shot
it shows, in up to, not including, out (in < out ≤ 65 536), its length L = out
− in; and the transition into it: {"kind": "cut"}, or {"kind": "dissolve", "frames"}, a
dissolve over frames ≥ 1 frames. With dᵢ the dissolve frames into event i (0 for a cut):
the first event’s transition is a cut, and dᵢ + dᵢ₊₁ ≤ Lᵢ for every event i (with
d = 0 past the last), so no frame shows more than two events. Otherwise record_malformed.
A storyboard’s order is the events {"shot": i, "in": 0, "out": duration of i, "transition": {"kind": "cut"}}, one for each position i of order, in its order.
6.2 The timeline
Events run in order. Event i starts at output frame Sᵢ: S₀ = 0 and Sᵢ = Sᵢ₋₁ + Lᵢ₋₁ − dᵢ, and occupies the output frames Sᵢ up to, not including, Sᵢ + Lᵢ. The output has N = Sₘ₋₁ + Lₘ₋₁ frames, m the number of events. A dissolve into event i overlaps the last dᵢ frames of event i − 1 with the first dᵢ of event i.
Output frame f shows frame in + (f − Sᵢ) of event i’s shot for each event i that
occupies it: one event, or two in a dissolve. The cuts are S₁, …, Sₘ₋₁: the frames where
each event after the first begins to show, cut or dissolved.
7. The framing measure
The framing measure of a subject in a frame of width W and height H is computed from
its box (x, y, w, h) and the angle of the view it is drawn from:
-
size: with q = ⌊1000 · h / H⌋, the subject’s height in thousandths of the frame’s:
class q extreme_widebelow 250 wide250 to 699 full700 to 1099 medium_wide1100 to 1499 medium1500 to 2199 medium_close2200 to 3199 close3200 to 4999 extreme_close5000 or more The box is the subject’s whole extent, so a close shot’s box runs far past the frame. The bounds are this measure’s: a director’s vocabulary maps onto them, and they define no craft’s terms.
-
position: the cell of a three-by-three grid holding the box’s centre: column c = min(2, max(0, ⌊3 · (2x + w) / (2W)⌋)) and row r = min(2, max(0, ⌊3 · (2y + h) / (2H)⌋)), named
R-Cwith Rtop,middleorbottomfor r 0, 1, 2 and Cleft,centreorrightfor c 0, 1, 2:top-left…middle-centre…bottom-right. -
angle: the angle of the view.
A directed class matches when the measure gives it. A directed box matches when it equals the subject’s box member for member; a directed camera window when it equals the frame’s window.
8. The directed output
8.1 The record
The tool that made the output signs the directed output, kind "wai-directed":
| member | type | meaning |
|---|---|---|
kind | string | "wai-directed" |
direction | array | 1 to 64 link hashes of the direction records it was made under, the storyboard first; none twice |
directors | array | 1 to 64 keys (32 B hex), the people who directed it; none twice, none the tool’s key |
check | string | replicate or attest |
attestation | object or null | null for replicate; for attest, {"generator", "class"}: a term naming what made the frames, and a term naming the class of method that stated the layout (a tracker, a detector, the generator’s own conditioning) |
layers | array | 0 to 256 layers (§8.2) |
clips | array | 1 to 256 clips (§8.3) |
timeline | array | 1 to 1024 events (§6.1): the edit as made |
output | object | {"width", "height", "frames", "digest", "content_hash"}: the output’s frame size, its number of frames (1 to 2^20), the digest of its frames (§8.4), and the content hash (jwp-receipts §2.1) of the object it was published as, or null |
governs | array | 0 to 1024 entries (§10.1) |
issued_at | date-time | when the tool signed it |
signer_pubkey, sig | §2: the tool’s key |
replicate states that every frame is the render of §9 over the layers; attest that something
else made the frames and the layout is the tool’s statement, of the class it names. The record
reads only if its timeline does (§6.1).
8.2 Layers
A layer is a picture reference with its source, {"samples", "width", "height", "source"}; no samples digest twice. Its source is one of:
{"generator", "model", "prompt", "seed", "steps"}withgenerator"wai.generate.spectral": the picture that generator makes with the model file whose SHA-256 ismodel(32 B hex), the prompt (0 to 256 bytes, each from 0x20 to 0x7E other than"and\), the seed (0 to 2^32 − 1) and the steps (1 to 64), at the layer’s size: SPEC §5, Payload ofwai.generate.spectral,generate(model, prompt, seed, steps, height, width);{"generator", "model"},generatorany other term: a picture another generator made, with the model whose digest ismodel;null: a picture given as it is, such as a person’s drawing.
A layer is named by its samples digest wherever a clip or a direction record names a picture.
8.3 Clips
A clip is the layout of every frame of one shot as made, {"frames", "plate", "camera", "subjects", "holds"}; the clips are the storyboard’s shots, in order:
frames: its number of frames, from 1 to 65 536;plate: the samples digest of the picture the frames are drawn over, ornull(§8.5);camera:null, or one rect per frame: the camera window over the plate;subjects: 0 to 16 entries, in drawing order, each{"subject", "views", "boxes"}: a subject’s position; one view position per frame (the view drawn); and one box per frame;holds: 0 to 64 entries{"from", "to", "rects", "source"}: framesfromup to, not including,to(from<to≤frames), 1 to 64 rects and a samples digest: the regions held, as made.
A camera, views or boxes whose length is not frames is record_malformed.
8.4 The frames’ digest
digest = BLAKE3("wai:direction-frames\x01" || D_0 || D_1 || … || D_{N−1})
with Dₖ the samples digest of output frame k, width × height RGB8 (§2), in order. A
frame is named by its samples digest, as a frame of a wai.video.int_motion clip is checked by
its state.
8.5 Layout rules
Against the storyboard, a directed output whose layout breaks one of these is layout_invalid,
or layer_missing where a picture it needs is not one of its layers:
- There is one clip per shot of the storyboard.
- Each clip’s subjects are subjects of the storyboard, and each view position is one of that subject’s views.
- For
replicate: each clip’splateis notnull, and is a layer (layer_missing); with no camera, the plate is the frame’s size; each camera window is a rect within the plate; each view drawn is a layer of the size the storyboard states for that view (layer_missingwhere it is not a layer). Forattest: eachplateisnull. - Each hold’s rects are within the frame; for
replicateits source is a layer (layer_missing) of the frame’s size. - Each timeline event names a shot of the storyboard and
outis at most its clip’sframes. - The output’s
widthandheightare the storyboard’s, and itsframesis the timeline’s N (§6.2).
9. The render
The render is pinned: every byte of every frame it makes is a pure function of the layers and the record, integer throughout.
9.1 A clip’s frame
Frame t of a clip, width W by height H, is drawn in this order:
- The plate. With no camera, out(X, Y) = plate(X, Y). With the camera window (cx, cy, cw, ch) of frame t, out(X, Y) = plate(cx + ⌊X · cw / W⌋, cy + ⌊Y · ch / H⌋): the window resampled to the frame by the nearest sample.
- The subjects, in the clip’s order. With the box (bx, by, bw, bh) of frame t and the view V of frame t, vw × vh: every pixel (X, Y) of the frame with bx ≤ X < bx + bw and by ≤ Y < by + bh is V(⌊(X − bx) · vw / bw⌋, ⌊(Y − by) · vh / bh⌋): the view scaled to its box by the nearest sample and drawn opaquely, the part off the frame dropped.
- The holds, in order: for each hold whose frames include t, every pixel inside each of its rects is the source’s pixel at the same position.
9.2 An output frame
Output frame f that shows one event is that event’s clip frame (§6.2). One that shows events i − 1 and i, in a dissolve of d = dᵢ frames, is a blend of their clip frames A and B: with j = f − Sᵢ + 1 (1 to d), each byte is
out = ⌊(A · (d + 1 − j) + B · j + ⌊(d + 1) / 2⌋) / (d + 1)⌋
so B’s weight rises by 1/(d + 1) each frame and neither picture is ever shown alone inside the dissolve.
10. Governance
10.1 What an element governs
An element (§3) governs the output frames that show what it directed, on the timeline the directed output states:
| element | the output frames it governs |
|---|---|
["shot", i] | each frame that shows any frame of shot i |
["keyframes", e] | each frame that shows a frame t of the entry’s shot with t in its keyed frames: from its first key’s t to the shot’s end where it has a track, and otherwise the t of each key that gives a class |
["region", j] | each frame that shows a frame of the region’s shot from from up to, not including, to |
["sequence"] | every output frame |
Within those frames, a region governs the samples inside its rects, and every other element every sample. A frame that shows two events is governed by what governs either.
The directed output states this in governs: one entry for each element of each record it
names, in the order of direction and then of each record’s elements (§3), each {"record", "element", "frames"}: the record’s position in direction, the element’s path and the frame
set (§2) it governs, [] for none. An entry that is not that element’s, or not its frame set, is
governance_mismatch. So a tool cannot claim for a director frames the director’s choice did not
govern, and cannot leave out frames it did.
10.2 The per-frame map
The frame map gives each output frame: the frames it shows, [shot, t] for each event
occupying it (§6.2); and directed_by, an object of four members, pixel, frame, shot and
sequence, each the positions in directors of the signers of the elements of that level that
govern it, ascending, none twice. It shows, frame by frame, which person directed the framing,
the timing, the held samples and the order, beside whatever made the frame’s samples: the
layers’ sources (§8.2) for replicate, the attested generator for attest.
10.3 The contribution record’s direct act
The contribution record, wai.media.contribution, reserves the act direct for this
extension. Each element that governs at least one frame becomes one step of that record’s session:
| member of the step | value |
|---|---|
party | the director: a human party whose key is the element’s record’s signer |
act | direct |
inputs | none |
output, region | null |
params | {"record", "element", "frames"}: the direction record’s link hash, the element’s path, and the frame set it governs (§10.1) |
check | replicate when the directed output’s check is replicate, so every check of §12.2 the element has ran on recomputed frames; attest otherwise |
| signer | the director |
Its dependency rule: the step introduces no sample and changes none; it adds the director to
directed_by of every sample the element governs (§10.1), in every frame of frames, and
leaves made_by as it is. A verifier of the contribution record holding the direction records and
the directed output recomputes frames by §10.1 and refuses a step whose frames differ, as
§12.2 refuses a governs entry. So the authorship map shows, per frame, that a person directed
framing and timing where a model made the samples. The contribution record’s revision of
2026-10-21 maps still pictures only; the step applies, sample by sample, in the revision that maps
video frame by frame, and to a still picture as an output of one frame.
11. Mappings
11.1 C2PA actions
For each director with at least one element that governs a frame, an accepted case gives one
C2PA statement, {"actions": A}, written in JCS: A is an actions assertion’s actions (C2PA 2.4
§18.15.1), one for each of that director’s elements that governs a frame, in the order of §10.1:
{"action": "c2pa.edited", "changes": C, "description": T}
T, the action’s description (C2PA 2.4 §18.15.4.1), by level:"directed shot: framing, duration and subjects","directed keyframes: framing over time","directed region: samples held to a reference"and"directed sequence: order, cuts and transitions".C, itschanges(C2PA 2.4 §18.15.4.6): one region map (§18.2) for each frame range of the element’s frame set, in order, each{"region": [{"type": "frame", "frame": {"start": a, "end": b − 1}}]}, a frame range of §18.2.2.4, whose end is inclusive. For a held region, one region map for each frame range and each rect, by range and then by rect, whoseregionholds that frame range and then{"type": "spatial", "shape": {"type": "rectangle", "unit": "pixel", "origin": {"x": X, "y": Y}, "width": W, "height": H}}, the rect, the coordinates as integers (a manifest in CBOR writes them as the floats §18.2.3 types them).
C2PA 2.4 §18.2.2.4 recommends temporal ranges for media over time. Direction is exact in frames,
and a frame’s time at a rate such as 24000/1001 has no exact decimal form, so the statement uses
frame ranges; a manifest that also wants times derives them from rate.
Each director’s statement is one actions assertion of the output’s manifest, labelled by C2PA
2.4 §6.4’s rule for several assertions of one type. It is never the first actions assertion,
which begins with the manifest’s c2pa.created or c2pa.opened action (C2PA 2.4 §18.15.2):
the generation’s, or the contribution record’s statement where there is one. Embedding the
statements in a manifest, and signing it, is C2PA’s.
11.2 Identity assertions (informative)
A director may give the output’s manifest an identity assertion (Identity Assertion 1.2 §5.1,
label cawg.identity) whose referenced assertions include the actions assertion of §11.1 that
holds that director’s actions, which §6.1 there gives as an example of an assertion that lets an
actor describe its relationship to the content. A role of §5.1.2 there is the director’s own
statement; this extension derives none. The director’s key in the records is an Ed25519 key, and
the relation between it and the assertion’s credential is the director’s to state.
11.3 An open timeline interchange format (informative)
A sequence (§6) maps onto OpenTimelineIO’s serialized schemas, release 0.18.1: a Timeline.1
whose tracks is a Stack.1 holding one Track.1 of kind "Video", whose children are, for
each event i in order, a Clip.2 whose source_range is a TimeRange.1 from start_time
in to a duration of Lᵢ − dᵢ₊₁ frames, each a RationalTime.1 at rate num / den,
naming the shot’s clip as its media; and, before each event entered by a dissolve, a
Transition.1 of transition_type "SMPTE_Dissolve" with in_offset 0 and out_offset dᵢ
frames. The cut point is then Sᵢ, the outgoing clip’s range ends there, and the dissolve
draws its last dᵢ frames from past that range, as this extension’s timeline shows them. An edit
decision list that records each event’s source in and out points and its record in point (Sᵢ),
with a cut or a dissolve of a duration, carries the same values. Neither format carries the
framing of §5 or the governance of §10, and neither is signed: they are the edit’s interchange,
and the direction records are its evidence.
12. Verification
12.1 Inputs
A verifier is given: the directed output record; the direction records it holds, in a list; the model files it holds, each named by its SHA-256; the pictures it holds, each a buffer named by its samples digest; and a policy of one member:
| member | values | default | meaning |
|---|---|---|---|
require | attested, exact | attested | the lowest status it accepts (not_exact) |
12.2 Steps
The verifier runs these steps and stops at the first refusal, which it reports with its reason
and its place: ["output"], ["record", i], ["direction", k], ["layer", i], ["clip", i],
["clip", i, t], ["timeline"], ["timeline", i], ["governs", i] or ["policy"], each i
a position in its list (a record’s among the records given, a direction record’s k among
direction), and t a frame of a clip.
- Reading (§2, §4–§6, §8): the directed output at
["output"], then each record given at["record", i]:record_malformed,bad_key,bad_signature. - The direction, each link of
directionin order at["direction", k]:- a record given has that link hash (
record_unknown); - the first is a storyboard and no other is; each keyframes or sequence record’s
storyboardis the first’s link hash (storyboard_mismatch); - its signer is one of
directors(signer_not_party); - no second record sets the sequence level, and no keyframes entry directs a shot an
earlier record’s entry directs (
direction_conflict); - against the storyboard, every shot, subject and angle it names exists; every key’s
tis below its shot’s duration; every key has one subject entry per subject of its shot; every region’stois at most its shot’s duration, its rects are within the frame and its source is the frame’s size; every event’soutis at most its shot’s duration (direction_inconsistent).
- a record given has that link hash (
- The check, at
["output"]: areplicaterecord with a layer of another generator thanwai.generate.spectral(§8.2) ischeck_mismatch: a float generator’s picture is never claimed exact. - The layout (§8.5): rule 1 at
["output"]; rules 2 to 4 clip by clip at["clip", i]; rule 5 event by event at["timeline", i]; rule 6 at["output"]:layout_invalid,layer_missing. - The layers, for
replicate, each in order at["layer", i]: a generated layer whose model file the verifier does not hold, or a given layer whose buffer it does not hold, islayer_missing; a held buffer that is notwidth·height· 3 bytes issamples_mismatch; a generated layer whose picture’s samples digest is notsamplesisregeneration_mismatch. - The frames, for
replicate, at["output"]: the verifier renders every clip frame (§9.1) and every output frame (§9.2) and computes the digest of §8.4; another digest thanoutput.digestisframe_mismatch. - The direction against the layout: each element of each direction record, in the order of
§10.1, against the clips and the timeline the directed output states:
["shot", i]: clip i’sframesis the shot’s duration (duration_mismatch, at["clip", i]); its subjects are the shot’s, in its order (subject_mismatch, at["clip", i]); each class of the shot’sframingmatches the measure (§7) of clip frame 0 (framing_mismatch, at["clip", i, 0]);["keyframes", e]: for each keyed frame t of the entry’s shot in ascending order, each subject’s box is its track’s value (§5.2), the camera window is the camera track’s, and, at a key, each class it gives matches the measure (framing_mismatch, at["clip", s, t], s the shot);["region", j]: the shot’s clip has a hold with the region’sfrom,to, rects and source (region_mismatch, at["clip", s]);["sequence"]: with the directed events (§6.1), the timeline holds the same shots in the same order (order_mismatch, at["timeline"]); then, event by event, each starts at its directed frame (cut_mismatch, at["timeline", i]); then each has its directed transition (transition_mismatch); then its directedinandout(duration_mismatch).
- Governance (§10.1), each entry in order at
["governs", i], and a missing entry at the position it would have:governance_mismatch. - Policy: an
attestrecord underrequireexactisnot_exact, at["policy"].
A sequence level no record sets is not checked: the order is then the tool’s own, and no element governs it.
12.3 Status
The outcome’s status is exact for replicate: the frames recompute, so the layout the
checks of step 7 read is what the frames hold, cut for cut and pixel for pixel. It is attested
for attest, with the attestation’s generator and class: the same checks ran on what the tool
stated, which nothing recomputed. An attest record is never reported exact.
12.4 What an accepted case states
output: the directed output’s link hash;status,generatorandclass: §12.3, the last twonullwhen exact;records: each record ofdirection, in order, itskind, its link hash, its signer’s position indirectorsand itslevels;elements: each element in the order of §10.1, itsrecord,element,level,directorand governedframes;cuts: the cuts of the stated timeline (§6.2);frames: the frame map (§10.2), one entry per output frame;frame_sha256: forexact, the samples digest of each output frame, in order;nullotherwise;layers: each layer’s status, in order:regeneratedorheldforexact,statedforattested;c2pa: the statements of §11.1, each{"director", "statement"}, by director.
13. Reasons
The codes this extension adds, each with where it is raised:
| reason | raised at |
|---|---|
record_malformed | §2, §3, §4, §5, §6.1, §8, §12.2 step 1 |
bad_key | §2 |
bad_signature | §2 |
record_unknown | §12.2 step 2 |
storyboard_mismatch | §12.2 step 2 |
signer_not_party | §12.2 step 2 |
direction_conflict | §12.2 step 2 |
direction_inconsistent | §12.2 step 2 |
check_mismatch | §8.1, §12.2 step 3 |
layout_invalid | §8.5 |
layer_missing | §8.5, §12.2 step 5 |
samples_mismatch | §12.2 step 5 |
regeneration_mismatch | §8.2, §12.2 step 5 |
frame_mismatch | §8.4, §12.2 step 6 |
duration_mismatch | §4.2, §6.1, §12.2 step 7 |
subject_mismatch | §4.2, §12.2 step 7 |
framing_mismatch | §5, §7, §12.2 step 7 |
region_mismatch | §5.3, §12.2 step 7 |
order_mismatch | §6, §12.2 step 7 |
cut_mismatch | §6.2, §12.2 step 7 |
transition_mismatch | §6.1, §12.2 step 7 |
governance_mismatch | §10.1, §12.2 step 8 |
not_exact | §12.1, §12.3 |
14. Conformance
live-conformance/direction/ holds the corpus, in one file,
vectors.json, written from fixed seeds by wai_direction_vectors gen. It holds the model file
of wai.generate.spectral its layers are generated with, the pictures given as they are, the
measures (the framing measure of §7 alone: boxes at both sides of every size bound and every
column and row bound, past every edge and at the limits of a signed integer, each with the classes
it measures), the interpolations (§5.2 alone: each mode, rising and falling, at the keys,
between them and with floors below zero, each with its value), and the vectors: each a case
of §12.1, its outcome and, accepted, everything §12.4 states; refused, its reason and its place.
Its frames are small (64 × 36 pixels at 24 frames a second), so every one recomputes. It has:
- Accepted,
exact: a storyboard of three shots and two subjects, a cinematographer’s keyframes (a camera that eases across a plate in one shot, a push in from a medium to a close framing in another) and a held region of a drawn reference, and an editor’s sequence that reorders the shots, trims one and dissolves into it, every frame recomputed; the same storyboard fixing its own order, with no other record; a storyboard and keyframes with no sequence level, the order then the tool’s own; and the first under a policy that requires exact. - Accepted,
attested: the first direction with frames a float generator made, the layout stated by a tracker. - Refused: each reason of §13 at least once, each a control of one defect: among them a cut one frame from its directed frame, a keyframe box one pixel from its track, a size class the box does not measure, a direction record signed by a key that is not a director’s, the edit’s shots in another order, a float generator’s plate claimed exact, frames that do not recompute, a governance entry claiming frames its element does not govern, and a missing held region.
A conforming verifier gives every vector the outcome, the reason, the place and the statements
the corpus states. wai_direction_vectors verify and the test tests/direction.rs check that
the file regenerates byte for byte, that wai-rs gives every vector its outcome, that the corpus
refuses with every reason of §13, and that §13 lists exactly the reference’s codes.
live-conformance/direction_verify.py is the second implementation, written from this text: it
shares no code with wai-rs; it runs the generator in its own arithmetic and checks it first
against the generator’s conformance vector (wai-rs/tests/vectors/generate/sha256.json),
renders every frame, interpolates every track and measures every box itself, writes every
statement and C2PA statement itself, reads records with synthesis’s second implementation’s
JSON reader, verifies every signature with WAI’s strict rule (claim-conformance/_ed25519.py)
and every link hash with BLAKE3 written from its specification (claim-conformance/_blake3.py);
it mutates copies of the corpus and requires every copy to fail; and it drops each of its own
checks in turn and requires the corpus to catch the weakened verifier.
15. Security and privacy considerations
- What is checked and what is stated. A direction record is its director’s signed
statement of a choice; WAI checks the output against it, not whether the director made the
choice unaided. Under
replicatethe frames recompute, so the layout the checks read is what the frames hold: a cut, a box, a held sample is exact. Underattestthe layout is the tool’s statement, of the class it names, and the outcome says so. - No enrolment without a signature. A person is credited with direction only for elements of records that person signed, and a directed output names its directors; a record by any other key is refused. A tool cannot credit or blame a director for frames the director’s elements do not govern (§10.1).
- Direction is not making. Directing a frame does not make its samples. The frame map keeps
the two apart:
directed_byper level beside the layers’ or the generator’s making, never one merged figure, and an origin claim keeps its class (synthesis §3.2). - Not a judgment. The records state facts frame by frame; they rank no contribution. What any of it means for authorship is decided elsewhere, by others, under their own rules.
- Privacy. Records name directors by keys, never by name; subjects by reference pictures,
which a director discloses as it chooses. A layer’s prompt is in the clear in the directed
output; a tool that keeps prompts private names the layer as given (
sourcenull) and commits the prompt in a contribution record instead. This extension sets no consent requirement for a service’s own mechanics: rendering under a director’s records, measuring a frame, stating governance. Using a distributor’s WAI service is the person’s agreement to how that service works. Where a regime asks for a lawful basis for processing a director’s key or a subject’s reference picture, the service records it in alawful-basisclaim (jwp-receipts §2.4.6).