open-standards/wai/extensions/live-moderation.md
Transaction Science 909f634a2e wai: register wai.media.takedown — registry, wire identifiers, SPEC §5, CHANGELOG, the CI step, the corpus index
The record and its seven document kinds (wai-takedown, -clocks, -policy, -notice,
-match, -purge, -counts) in codecs/registry.rs, the export regenerated
(capabilities.json, vocab/v2.ttl, REGISTRY.md's table); SPEC §5's record list and a
paragraph; the CHANGELOG entry; the wai-conformance step (the module's unit tests,
tests/takedown.rs, takedown_verify.py) and its path; live-conformance/README.md; and a
pointer from live-moderation §1 to the sibling record.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-21 19:46:20 -04:00

51 KiB
Raw Permalink Blame History

WAI Extension: Live Moderation

Status: Draft. Registers the record wai.live.moderation: moderation for rooms. The room authority signs a review policy, WMP1: which event kinds and media are held for review before delivery or reviewed after it, by whom, the deadline of each, the fallback when no decision arrives in time (allow, deny or hold), and the reason codes in use, each with its ground. A moderator's verdict is a decision event of live-events (§4.11 there), unchanged, followed by a meta event whose payload is this extension's statement, WMS1: what kind of thing the decision targets (an event, or a media object such as a recorded chunk), the decision it supersedes, its basis (own initiative, a notice, a complaint, or the policy's fallback), whether automated means detected the content or took the decision, and its ground, explanation and facts. Decisions on one target form a line by digest, so a later decision overrides an earlier one and the outcome of an appeal is a decision too. Anyone can submit a signed notice, WMN1, about an event or an object, and anyone affected a complaint about a decision. A restricted item is withheld, and where it is withheld from a chain, a recording or a clip, its absence is proven by the decisions that restrict it. Each restricting decision maps, deterministically, onto one record of the statement-of-reasons format of the database the European Commission keeps under Regulation (EU) 2022/2065. No new transport. The reference implementation is the live_moderation module of wai-rs (feature live_moderation); the corpus is live-conformance/moderation/, and its second implementation is live-conformance/live_moderation_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision, each pinned to the version named: Regulation (EU) 2022/2065 (OJ L 277, 27.10.2022, p. 1), Articles 16, 17, 20 and 24(5), as the Publications Office serves its only consolidated version, 02022R2065-20221027, with no amending act recorded and no English corrigendum; the DSA Transparency Database's "API and schema" documentation and its explanation of the statement attributes (transparency.dsa.ec.europa.eu), for the submission schema in force from 1 July 2025, which the documentation's changelog calls API v2 and which is still submitted at /api/v1/statement, as the database's published source has it at revision fc3b1fd5 (2026-09-23) of the documentation and revision bf0afeb8 (2026-06-09) of the submission's validation rules; and Commission Implementing Regulation (EU) 2024/2835, Annex II, whose categories the database uses. An article or paragraph number in this text is the Regulation's unless it says otherwise.

1. Scope

In scope: the review policy, its signature, its rules and the policy in effect at each instant (§3); the decision event as this extension reads it, the statement, who may decide, lines of decisions and fallback decisions (§4); notices and complaints (§5); the items under review, their states, and what a relay and a sink do with them (§6); verification, with the gaps that restricted items leave explained (§7); the effect of a restriction on a stream, a recording and a clip (§8); and the statement-of-reasons export (§9).

Out of scope: a transport; how a moderator or a classifier reaches a verdict; how a notice or a complaint reaches the room authority; the notification of a submitter or an affected participant (Article 16(5), Article 17(1), Article 20(5)); the submission of a record to the database, which is an authenticated HTTP request this extension does not define; and the restriction of accounts, payments or the provision of a service (Article 17(1)(b) to (d)), which no WAI record expresses. This extension states what the records carry and how they map onto the database's format. Which decisions a service takes, and which records it submits, is not its subject. The takedown of an object beyond a room (a catalogue or on-demand object, and every rendition and cache derived from it) is takedown's, a sibling record that leaves these records and their version unchanged.

Relationship to the core spec: wai.live.moderation is a record (SPEC §5): a signed record a WAI tool emits and verifies. It names no payload format and is not dispatchable.

Relationship to live-events: this extension changes nothing in it. Its decision payload (§4.11 there) holds a verdict, a target digest and a reason code and no more, so the statement rides in the meta kind, which live-events leaves to applications (§4.2 there), and is bound to its decision by the event chain (§4.2 here), as timed-text's annotation is bound to its caption. A decision's target is the digest of an event, as live-events has it, or, where its statement says so, of a media object (§4.1).

Relationship to live-rooms: the room descriptor's review field (§3.6 there) fixes the room's review at its creation; a review policy refines it and never contradicts it (§3.5). The moderate power (§3.3 there) is the power to decide.

Relationship to live-recording: a restriction changes what a recording package holds and what a clip presents (§8). A clip's inclusion proof does not change: it is over the chunks' descriptors, which a restriction leaves in place.

2. Terms

  • Room, room clock, principal, event, chain, grant, room key, merged order: as in live-events §2 and live-rooms §2 and §9.2. A room key is the room authority's key or the clock's.
  • Item: an event of a decidable kind (§4.1), or a media object (§4.1), that a policy reviews or a decision targets.
  • Decision: a decision event (live-events §4.11). Its statement is the WMS1 payload of the meta event that follows it in its chain (§4.2).
  • Line of a target: the decisions on it, the first superseding none and each later one superseding the one before (§4.5). Its last decision is the standing decision.
  • Restricting verdict: deny or redact. An item whose standing decision restricts it is restricted.
  • Withheld: not delivered to a subscriber session, not presented by a sink, or, for redaction, not kept in a recording package (§8).
  • Reviewer: a principal a policy names to decide on the items it reviews (§3.1).
  • Moderator: a principal whose grant in effect has the moderate power (live-rooms §3.3), or a room key.

Integers are little-endian. A digest is SHA-256 (FIPS 180-4), 32 bytes. Ed25519 is RFC 8032's, verified strictly (SPEC §7 Signatures): a key that fails step 1 or 2 is bad_key, and a signature that fails a later step is bad_signature. A text field is a u16 length and that many bytes, which are UTF-8 (RFC 3629): no overlong form, no surrogate, nothing above U+10FFFF.

3. The review policy

3.1 Layout

field size meaning
magic 4 WMP1 (ASCII)
version 1 1 in this revision
room 32 the room id (live-rooms §3.2)
prev 32 the digest of the policy this one replaces (§3.4); 32 zero bytes for the first
effective 8 u64: the room-clock instant from which this policy applies; 0 for the first
reviewers 1 + 32 × n a u8 count (0 to 16), then each reviewer's Ed25519 key
flaggers 1 + 32 × n a u8 count (0 to 16), then each key the authority recognises as a trusted flagger's (§9.3 source_type)
entries 1 + 10 × n a u8 count (0 to 32), then each entry: scope (u8), code (u8), mode (u8), deadline_ms (u32), fallback (u8), fallback_reason (u16)
reasons 1 + … a u8 count (1 to 64), then each reason code: code (u16), ground (u8), category (u8), reference (text), url (text)
territory 1 + 2 × n a u8 count (1 to 30), then each state's code (§9.3 territorial_scope), two ASCII capitals
redress text how an affected participant can seek redress (§9.4, Article 17(3)(f))
sig 64 §3.2

An entry says how items of one kind are reviewed:

  • scope 1: code is an event kind (live-events §4), and it must be a decidable kind (§4.1); scope 2: code is a media kind (live-rooms §6.1: 1 audio, 2 video, 3 screen), and the entry reviews every media object of that kind.
  • mode 1 pre: an item is held from delivery until it is decided or its deadline passes; mode 2 post: an item is delivered at once and reviewed after.
  • deadline_ms: how long after the item's t a decision is awaited, in milliseconds of room clock, above 0.
  • fallback, what applies at the deadline when no decision has come: 1 allow, 2 deny, 3 hold (the item stays withheld until a decision comes).
  • fallback_reason: the reason code a fallback that denies or holds applies under; 0 when fallback is allow.

A reason code is one entry of the room's vocabulary:

  • code: the value a decision's reason (live-events §4.11) carries, above 0;
  • ground: 1 illegal content, 2 incompatible with the terms of the service;
  • category: the database category it maps to, by its number in §9.5;
  • reference: the legal provision, or the clause of the terms, the code rests on: 1 to 500 code points;
  • url: empty, or an https:// URL where the terms or the provision can be read: https:// then bytes of ! to ~ (0x21–0x7E), at most 500 bytes in all.

3.2 Digest and signature

The digest of a policy is SHA-256 over its record from magic through redress. The signature is Ed25519 by the room descriptor's authority over the 27 bytes wai:live-moderation-policy (ASCII) and 0x01, followed by the bytes the digest covers.

3.3 Reading and rules

A reader reads exactly the bytes it is given as one policy, in the order of §3.1, and refuses at the first of these that holds:

  1. Fewer than 4 bytes: truncated. A magic other than WMP1: malformed. No byte after the magic: truncated. A version other than 1: unknown_version.
  2. The bytes end before a field the layout requires: truncated.
  3. Bytes after sig: malformed.

It then holds the policy to these rules, and refuses one that breaks any of them as policy_malformed:

  • a count is within its range;
  • the reviewers are in strictly ascending byte order, and so are the flaggers;
  • the entries are in strictly ascending order of (scope, code), with scope 1 or 2, code as scope requires, mode 1 or 2, deadline_ms above 0, fallback 1, 2 or 3, and fallback_reason 0 when fallback is 1 and a code of reasons otherwise;
  • the reason codes are in strictly ascending order of code, each above 0, with ground 1 or 2, category 1 to 16, and reference and url as §3.1 has them;
  • the territory's codes are in strictly ascending order, each one of the thirty of §9.3;
  • every text field is UTF-8.

Then, in the steps of §7.2: its room is the room id (wrong_room); its signature verifies (bad_key, bad_signature); its link (§3.4, policy_unlinked); and the room's review field (§3.5, policy_conflicts_room).

3.4 The policy in effect

A verifier is given a room's policies in a list. The first has prev zero and effective 0. Each later one has as prev the digest of the one before it in the list, and an effective above that one's. A policy that breaks this is refused policy_unlinked. The policy in effect at room-clock instant t is the last of the list whose effective is at or below t. A room given no policy has none in effect at any instant.

A policy is replaced, never amended: the authority signs a new one, chained to the old, with the instant it takes effect. Each item is reviewed under the policy in effect at its t, and each decision is checked against the policy in effect at the decision's t.

3.5 The room's review field

The room descriptor's review field (live-rooms §3.6) names the event kinds held for review, one deadline and one fallback, allow or deny; the room id commits to it, so every principal that joined the room joined under it. Every policy of the room MUST keep it: for each kind that field names, the policy has an entry of scope 1 for that kind, with mode 1 (pre), the field's deadline_ms and the field's fallback. Otherwise policy_conflicts_room. A policy may add entries for other kinds and for media, and name reviewers, flaggers and the vocabulary, which the field does not hold.

4. Decisions

4.1 The decision event

A decision is a decision event (live-events §4.11): verdict (1 allow, 2 deny, 3 redact), target (32 bytes) and reason (u16). Its statement (§4.2) says what target names:

  • an event: the digest of an event of the room (live-events §3.2), of a decidable kind: meta (other than a statement), cue, chat, reaction, poll_open, vote, poll_close, marker or caption. A control, a decision, a take_commit and a lifecycle event are not decided on: an appeal of a decision is a decision that supersedes it (§4.5), not one that targets it;
  • a media object: its digest. A chunk of a take is named by its leaf (capture-binding §4.2), which binds its index, its capture range, its length and the digest of its bytes, so the name is the chunk's in its take; any other object, by the digest its receipt names it with (jwp-receipts §2.1).

A decision without a statement targets an event, supersedes nothing, and is of the moderator's own initiative.

Verdicts:

  • allow: the item is shown;
  • deny: access to the item is disabled: it is withheld from delivery and presentation, and a recording keeps it, so that a later decision can restore it (§8);
  • redact: the item is removed: it is withheld as for deny, and a recording does not keep it (§8.2).

A restriction lasts until a later decision of its line supersedes it. A restriction for a set time is a decision and, at the time's end, an allow that supersedes it.

4.2 The statement

A decision's statement is the payload of a meta event whose first four bytes are WMS1, by the same principal, at the decision's seq + 1 and at the decision's t.

offset size field meaning
0 4 magic WMS1 (ASCII)
4 1 version 1 in this revision
5 1 target_type 1 an event, 2 a media object (§4.1)
6 32 target_prev for an event: the target's prev (live-events §3.1), so that a chain withholding it stays linked (§7.3); for an object: 32 zero bytes
38 32 supersedes the digest of the decision event this one supersedes (§4.5); 32 zero bytes for none
70 1 basis 1 the moderator's own initiative, 2 a notice, 3 a complaint, 4 the policy's fallback (§4.6)
71 32 notice for basis 2 and 3: the digest of the notice or complaint it answers (§5); otherwise 32 zero bytes
103 1 automated bit 0: the content was detected or identified by automated means; bits 1 and 2: how far the decision was taken by automated means, 0 not, 1 partially, 2 fully; every other bit 0
104 1 ground 1 illegal content, 2 incompatible with the terms
105 text ground_ref the legal provision or the clause of the terms relied on; empty when the reason code's reference is the one
… text explanation why the item is considered illegal, or incompatible, on that ground
… text facts the facts and circumstances relied on in taking the decision

4.3 Reading a statement

A statement is refused statement_malformed when: it ends before a field or holds bytes after facts; its version is not 1; target_type is not 1 or 2; target_type is 2 and target_prev is not zero; basis is not 1 to 4; notice is zero for basis 2 or 3, or not zero for basis 1 or 4; automated sets a bit above bit 2, or bits 1 and 2 read 3; basis is 4 and automated's bits 1 and 2 do not read 2, or supersedes is not zero; ground is not 1 or 2; or a text field is not UTF-8.

The meta payload is bounded by the principal's policy (live-events §5, max_payload), 1024 bytes by default, which bounds the three texts together.

4.4 Who decides

A decision is the event of a moderator. The room verifier already holds every decision to it: a grant whose policy permits decision has the moderate power (live-rooms §3.3, §4.3 step 5), and a participant's event is verified under its grant in effect at its t (live-rooms §9.2 step 4). A decision by a principal without the moderate power is therefore refused by the room, kind_not_permitted, before this extension reads it. A room key decides as the authority, without a grant.

Where the target is an item a policy reviews (§6.1) and the policy in effect at the decision's t names reviewers, the decider is one of them or a room key: otherwise not_reviewer.

4.5 Lines: superseding by digest

The decisions on one target form one line. The first supersedes none; each later decision names in supersedes the line's last decision before it, so that it overrides it from its own t. A verifier checks each decision, in the merged order, against the line as it stands:

  • supersedes zero while the target already has a decision: supersede_gap;
  • supersedes naming a decision the verifier does not hold: supersede_unknown;
  • naming a decision on another target: supersede_mismatch;
  • naming a decision of this target that is not the line's last (one another decision already supersedes): supersede_gap.

An appeal is a complaint (§5) against a decision, and its outcome is a decision of basis 3 that supersedes the decision complained of and names the complaint. It may reverse that decision (allow after deny) or confirm it.

4.6 Fallback decisions

At an item's deadline with no decision, its entry's fallback applies (§6.2) whether or not any record says so. A room key SHOULD sign it, as a fallback decision at or after the deadline: basis 4, automated fully, supersedes zero, the entry's fallback as its verdict (1 allow, 2 deny) and the entry's fallback_reason as its reason. A fallback decision lets a subscriber that never received the item explain its absence (§7.3), and gives the restriction a signed record to export (§9). A fallback of hold has no decision: the item waits.

A fallback decision is refused fallback_not_in_policy when: its author is not a room key; its target is not an item reviewed under an entry whose fallback is allow or deny; or its verdict is not that fallback, or its reason not the entry's fallback_reason. It is refused fallback_early when its t is before the item's deadline. It supersedes nothing (§4.3), so a target that already has a decision refuses it by its line (§4.5, supersede_gap): a decision by the deadline forestalls the fallback.

5. Notices and complaints

5.1 Layout

A notice (Article 16) says that an item is illegal content, or incompatible with the terms; a complaint (Article 20) contests a decision. Anyone can sign one with a key of their own; neither needs a grant of the room.

field size meaning
magic 4 WMN1 (ASCII)
version 1 1 in this revision
room 32 the room id
type 1 1 a notice, 2 a complaint
target_type 1 a notice: 1 an event, 2 a media object (§4.1); a complaint: 1, the decision event complained of
target 32 the target's digest: the exact location of the information (Article 16(2)(b))
t 8 u64: the room-clock instant at which it was submitted
ground 1 a notice: 1 illegal content, 2 incompatible with the terms; a complaint: 0
good_faith 1 1: the submitter confirms its bona fide belief that what it states is accurate and complete (Article 16(2)(d)); 0: it does not
ground_ref text the provision or clause the submitter cites; may be empty
statement text the submitter's explanation of its reasons (Article 16(2)(a)), or of its complaint
contact text the submitter's name and email address (Article 16(2)(c)); may be empty
submitter 32 the Ed25519 key that signs it
sig 64 §5.2

5.2 Digest, signature and rules

The digest is SHA-256 over the record from magic through submitter. The signature is Ed25519 by submitter over the 27 bytes wai:live-moderation-notice (ASCII) and 0x01, followed by the bytes the digest covers.

A reader reads it as §3.3 steps 1 to 3 read a policy, with WMN1 for WMP1, and refuses as notice_malformed one in which: type is not 1 or 2; a notice's target_type is not 1 or 2, or its ground not 1 or 2; a complaint's target_type is not 1 or its ground not 0; good_faith is not 0 or 1; a text field is not UTF-8; or whose digest is an earlier notice's in the list. Then its room is the room id (wrong_room), and its signature verifies (bad_key, bad_signature).

A decision of basis 2 names a notice (type 1) and one of basis 3 a complaint (type 2) that the verifier is given, otherwise notice_unknown. A notice's target is the decision's target, and its target_type the statement's; a complaint's target is the decision the answering decision supersedes; and its t is at or before the decision's t. Otherwise notice_mismatch. A complaint may be submitted by anyone the decision affects, the participant whose item it restricts or the submitter of the notice it answers among them; this extension does not check who.

contact is personal data. No export carries it (§9.7).

6. Review

6.1 Items

An item under review is:

  • an event the verifier holds, of a decidable kind (§4.1), whose kind an entry of scope 1 of the policy in effect at its t names, and whose author is neither a room key nor a principal whose grant in effect at its t has the moderate power; or
  • a media object the verifier is given (§7.1), whose media kind an entry of scope 2 of the policy in effect at its t names.

Its entry gives its mode, its deadline, t + deadline_ms × 1000, and its fallback. An item that is not under review but that a decision targets is an item too, with no entry.

6.2 States

An item's state at room-clock instant x, from its t on, is:

  1. when a decision on it has its t at or below x: the verdict of the last such decision of its line, allowed, denied or redacted;
  2. otherwise, an item with no entry: shown;
  3. otherwise, before its deadline: held in mode pre, pending in mode post;
  4. otherwise (at or after the deadline, with no decision): fallback_allow, fallback_deny or fallback_hold, by its entry's fallback.

The fallback applies at the deadline unless a decision other than a fallback decision (§4.6) has its t at or below the deadline; a fallback decision records the fallback, and does not forestall it.

An item is visible in the states shown, pending, allowed and fallback_allow, and withheld in every other. A decision decides an item from its own t whatever came before it, so a moderator's decision after a fallback overrides the fallback, and an appeal's outcome overrides the decision it supersedes. A moderator's decision at the deadline itself is a decision by the deadline, and no fallback applies.

An item's timeline is the sequence of its states from its t: one entry at its t, one at its deadline when the fallback applies, and one at each decision on it in the merged order. The entries are in the order of their instants and, at one instant, in that order (the item's, the fallback's, the decisions'). Each entry is the instant, the state, the decision's digest where a decision made it, and whether the state is visible. The item's state at an instant is that of its last entry at or before it.

6.3 What a relay does

A relay that delivers a room's tracks under a policy applies it to each subscriber session:

  • An item in state held is delivered at once to the reviewers (or, with none named, to every present principal whose grant in effect has the moderate power) and to the room keys, and is withheld from every other subscriber session. The relay keeps it until its state becomes visible, when it delivers it to the sessions it withheld it from, or until a decision denies or redacts it. The deadline is the longest it waits for a decision before the fallback applies: at the deadline, under allow it delivers the item; under deny it withholds it for good unless a later decision allows it; under hold it keeps it until a decision comes, for as long as the session lasts.
  • An item in state pending is delivered at once. At the deadline with no decision, under deny or hold the relay stops delivering it, to a session that joins later or fetches it again.
  • A restricted item is withheld from every session but the reviewers' and the room keys', and a relay MUST NOT deliver an event or object in a withheld state to any other session, live, in a join set (live-events §8.2) or in answer to a FETCH.
  • A relay that withholds an event goes on delivering the events after it in its chain, so a session sees a gap (live-events §6.2). When the event is delivered later, the session fills the gap as live-events §6.2 has a receiver fill one: by verifying the chain again from its start. A withheld event that stays restricted leaves a gap, which the decisions that restrict it explain (§7.3).
  • An event a relay withholds from a session is not delivered to it, and its per-hop record (live-metering §3.3) does not count it as delivered there.

A relay that signs fallback decisions (§4.6) does so as the room's clock.

6.4 What a sink shows

A sink shows an item at instant x only when its state at x is visible, by the decisions it holds. Holding a decision's event and statement, it SHOULD NOT show what the decision restricts (live-events §4.11). A sink that holds a decision but not its target shows nothing of the target, and states the decision's verdict and reason in its place where it shows the chain. A sink that labels a restriction for a viewer takes the label from the statement and the reason code's vocabulary entry, never from the target's content.

7. Verification

7.1 Inputs

A moderation verifier is given what a room verifier is given (live-rooms §9.1: a descriptor, grants, a key-log head or none, and the chains), and:

  • policies: the room's review policies, in a list (§3.4);
  • notices: notices and complaints, in a list (§5);
  • objects: the media objects under moderation, each its digest, its media kind (1 audio, 2 video, 3 screen) and its t, the room-clock instant it reached the relay. For a take's chunks, the digest is the chunk's leaf and the t that of the commitment that adds it (capture-binding §5). The objects are the caller's statement, as the policies a live-events verifier is given are; no two have one digest.

7.2 Steps

It runs these steps and stops at the first refusal, which it reports with the reason and where:

  1. The room (live-rooms §9): a refusal is the moderation verifier's, at room with the room verifier's own place. A gap is not a refusal: the steps go on, and §7.3 says which gaps decisions explain.
  2. The policies, in the list's order (§3.3, §3.4, §3.5): the refusal, at the policy's index.
  3. The notices, in the list's order (§5.2): the refusal, at the notice's index.
  4. The statements. For each meta event the room accepted whose payload begins with WMS1, in the merged order: when its seq is 0, statement_misplaced; when the verifier holds its principal's event at seq − 1, that is a decision at the same t, otherwise statement_misplaced; when the verifier does not, the statement is an orphan of a delivery gap, and is read no further. A statement in place reads (§4.3), otherwise statement_malformed. At the statement's record.
  5. The gaps (§7.3): which gaps restricted items explain. No refusal.
  6. The decisions, in the merged order, each by §7.4: the refusal, at the decision's record.
  7. The outcome (§7.5).

A gap remains when the room has a gap that §7.3 does not explain. With a gap remaining, a check of §7.4 that fails because the verifier does not hold a decision, an event or a statement is unresolved, which is not a refusal; with none, it is the refusal the check names.

7.3 Explained gaps

A gap of principal P, seqs a to b (live-events §6.2), is explained when the verifier holds P's event at b + 1, and walking back from it reaches P's event at a − 1 (or the start of the chain, when a is 0) through restricted events only:

  1. D is the prev of the event at b + 1.
  2. For each s from b down to a: of the decisions the room accepted whose target is D, the last in the merged order has a statement of target_type 1 and a restricting verdict, otherwise the gap is not explained; seq s is withheld by that decision, and D becomes its statement's target_prev.
  3. D is then the digest of the event at a − 1, or 32 zero bytes when a is 0; otherwise the gap is not explained.

Each step is checked from what the verifier holds: the link from the gap to the event after it is the event's own prev, and each link inside the gap is a moderator's signed statement about an event it decided on, so no event is passed off as withheld that its chain does not hold at that place. An explained gap is not a delivery gap. A gap that is not explained stays one, with live-events' code delivery_gap.

The events an explained gap withholds are withheld targets: the verifier knows their digests and their places, not their kinds or their times.

7.4 Each decision

For a decision E of principal P, with target X, in the merged order, its statement being P's event at seq + 1 when that is a statement in place (step 4), unknown when the verifier does not hold that seq because a gap remains there, and none otherwise:

  1. The reason. A policy is in effect at E's t, its vocabulary has E's reason, and a known statement's ground is that code's: otherwise reason_not_in_policy.
  2. The target.
    • Of type 2: an object the verifier is given has digest X and a t at or below E's: otherwise target_unknown.
    • Of type 1 (or with no statement): when X is the digest of an event the room accepted, that event comes before E in the merged order, otherwise target_unknown; it is of a decidable kind, otherwise target_not_decidable; and a known statement's target_prev is its prev, otherwise target_prev_mismatch. When X is a withheld target (§7.3), these hold. Otherwise target_unknown, unresolved while a gap remains.
  3. The reviewer (§4.4): not_reviewer. Not made for a withheld target.
  4. The line (§4.5): supersede_gap, supersede_unknown (unresolved while a gap remains) or supersede_mismatch. Not made for an unknown statement; with none, supersedes is zero.
  5. The basis, for a known statement: of basis 2 or 3, §5.2 (notice_unknown, notice_mismatch); of basis 4, §4.6 (fallback_not_in_policy, fallback_early), its entry's checks not made for a withheld target.

E then becomes the last decision of X's line.

7.5 Outcomes and what a verification states

  • refuse: the reason and where (§7.2).
  • gap: no refusal, and a gap remains: the gaps that remain, under the code delivery_gap, and what accept states, computed from what the verifier holds.
  • accept: no refusal and no gap remaining.

With accept or gap, the verifier states:

  • the room's own outcome (live-rooms §9.4), accept or gap;
  • each explained gap: its principal, its seqs, and the decision withholding each seq;
  • each decision, in the merged order: its digest, its decider, its t, its target and target type, its verdict, reason and basis (none for an unknown statement), what it supersedes, the notice it answers, whether its statement is known, none or unknown, and whether it stands at the end;
  • each item, events in the merged order and then objects in the order given: its digest, its kind or media, its t, its entry (mode, deadline, fallback) or none, and its timeline (§6.2);
  • each decision's export (§9): the record, none for an allow, or the refusal.

8. Redaction in streams, recordings and clips

A restricted item is withheld, never replaced, and its absence is proven: whatever held it shows where it was, and the decision that restricts it shows why. A WAI chain, a take tree and a clip all bind their contents by digest, so an item cannot be swapped for a stand-in without breaking them; withholding keeps them whole, because each of them names the item without holding it.

8.1 Streams

An event withheld from a subscriber session leaves a gap in its author's chain, and the decisions that restrict it explain the gap (§7.3): the session knows the event existed, where, under which decision and why, and holds nothing of it. A media object withheld from a session is an object the relay does not forward. A restricted object's receipts, which name it by its digest, stay where they are.

8.2 A recording package

A recording sealed after a decision applies it to what it keeps:

  • A redacted chunk's file is left out of its take component, and a redacted event's record out of its principal's chain file. The take's manifest still lists the chunk's descriptor, and the next event of the chain still names the redacted one in its prev.
  • A denied item is kept, so that a later decision can allow it; every presentation of the recording (a clip, a playlist, a sink) withholds it.

The recording verifier (live-recording §4.4) then finds the take's chunk missing and the chain's gap, and fails complete with chunk_missing or delivery_gap. A moderation verifier explains them: a chunk the manifest lists, with no file, whose leaf a standing redaction names, and a gap §7.3 explains with redactions alone. Where every missing chunk and every gap is so explained, the recording is complete but for what was redacted.

What this revision implements of this section: nothing beyond §7.3 and §8.3. The reference explains gaps in a room's chains and verifies clips; it does not verify a package under moderation, and the corpus has no package vector.

8.3 Clips

A clip's chunks are proved against a commitment the device made while it captured, by a range proof over their descriptors (live-recording §5.3). A restriction leaves the descriptors in place, so the proof still verifies over every chunk the clip covers; what changes is which chunks' bytes the clip presents. A chunk is restricted when its leaf is the target of a standing decision that restricts it.

A clip under moderation is verified by live-recording §5.4 with two changes:

  1. In step (k), a restricted chunk is passed over: its bytes are neither required nor checked, and bytes given for it are not presented. The part presents the bytes of its other chunks, in order, less trim_in when its first chunk is not restricted and trim_out when its last is not.
  2. Then, after every part: a restricted chunk whose bytes are given is redaction_not_applied, at the first such chunk.

A clip that passes is accepted, and states what live-recording §5.4 states, and for each part the indexes of its restricted chunks, each with the decision that restricts it. A clip whose restricted chunk is restored by a later allow presents it again, and is refused clip_chunk_missing when its bytes are not given. A clip's inclusion proof thus verifies in every case, and a clip fails only with a stated reason: one of live-recording §9's, or redaction_not_applied.

9. The statement-of-reasons export

9.1 The format and its version

A restricting decision maps onto one statement of reasons in the form the DSA Transparency Database accepts at its single-statement endpoint (POST /api/v1/statement): the submission schema in force from 1 July 2025, which the database's documentation calls API v2, read at the revisions named at the head of this text. The export is the request body. An allow restricts nothing, and has no export.

9.2 The record

The record is one JSON object (RFC 8259) whose members are those of §9.3 that apply, in the order of §9.3, written with no whitespace. Each string is UTF-8, written with " as \", \ as \\, U+0008, U+0009, U+000A, U+000C and U+000D as \b, \t, \n, \f and \r, any other code point below U+0020 as \u and four lowercase hexadecimal digits, and every other code point as itself. There is no byte after the closing brace. A date is YYYY-MM-DD, the UTC date of the instant t0 + t of the room clock, t0 the descriptor's.

9.3 Members and their sources

"Closed" marks a member whose value is one of a list the database fixes; "free" a text the database accepts up to the length given, in characters (code points).

member value source form
decision_visibility ["DECISION_VISIBILITY_CONTENT_DISABLED"] for deny, ["DECISION_VISIBILITY_CONTENT_REMOVED"] for redact the decision's verdict closed, an array
decision_facts the statement's facts statement free, 1 to 5000
decision_ground DECISION_GROUND_ILLEGAL_CONTENT (1) or DECISION_GROUND_INCOMPATIBLE_CONTENT (2) statement ground closed
decision_ground_reference_url the reason code's url, only when it is not empty policy a URL, up to 500
illegal_content_legal_ground ground 1 only: the statement's ground_ref, or the reason code's reference when that is empty statement, policy free, 1 to 500
illegal_content_explanation ground 1 only: the statement's explanation statement free, 1 to 2000
incompatible_content_ground ground 2 only: as illegal_content_legal_ground statement, policy free, 1 to 500
incompatible_content_explanation ground 2 only: as illegal_content_explanation statement free, 1 to 2000
content_type a one-element array, by the target (§9.6) the target's kind or media closed, an array
content_type_other only with CONTENT_TYPE_OTHER: §9.6's text the target's kind free, up to 500
category the reason code's category, by §9.5 policy closed
territorial_scope the policy's territory, in its order policy closed, an array of the thirty codes AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MT, NL, NO, PL, PT, RO, SE, SI, SK
content_date the date of the target's t (an event's, or an object's as given), from 2000-01-01 to 2038-01-01 the target a date
application_date the date of the decision's t, from 2020-01-01 to 2038-01-01 the decision a date
source_type basis 1 and 4: SOURCE_VOLUNTARY; basis 2: SOURCE_TRUSTED_FLAGGER when the notice's ground is 1 and its submitter is one of the policy's flaggers, SOURCE_ARTICLE_16 when its ground is 1 and it is not, SOURCE_TYPE_OTHER_NOTIFICATION when its ground is 2; basis 3: the source_type of the decision it supersedes statement, notice, policy closed
automated_detection Yes when automated bit 0 is set, No otherwise statement closed
automated_decision AUTOMATED_DECISION_NOT_AUTOMATED (0), AUTOMATED_DECISION_PARTIALLY (1) or AUTOMATED_DECISION_FULLY (2), by automated bits 1 and 2 statement closed
puid the decision's digest, 64 lowercase hexadecimal digits the decision [a-zA-Z0-9-_], up to 500, unique

The policy is the one in effect at the decision's t. A notice of ground 2 is not a notice under Article 16, and the database's documentation names SOURCE_TYPE_OTHER_NOTIFICATION for it. A decision on a complaint keeps the source of the restriction it answers. The database's other members are not written: decision_monetary, decision_provision and decision_account (no WAI decision restricts payments, the service or an account), their _other texts, decision_visibility_other, account_type, incompatible_content_illegal, category_addition, category_specification and category_specification_other, content_id (a product number), content_language, every end_date_* (a restriction lasts until a decision supersedes it, which the database reads as indefinite when the date is left out), and source_identity (§9.7).

9.4 Article 17, element by element

Article 17 what it asks for WAI's field the member
(1)(a) a restriction of the visibility of specific items verdict deny or redact decision_visibility
(1)(b)–(d) payments, the service, the account none not written
(3)(a) removal, disabling, demotion or restriction; territorial scope; duration verdict; policy territory; the line (a restriction lasts until superseded) decision_visibility; territorial_scope; no end date
(3)(b) facts and circumstances; whether on a notice or on own initiative; the notifier's identity where strictly necessary statement facts; basis and the notice; none decision_facts; source_type; not written
(3)(c) use of automated means, in the decision and in detection statement automated automated_decision, automated_detection
(3)(d) for illegal content: the legal ground and why the item is illegal on it statement ground 1, ground_ref or the code's reference, explanation decision_ground, illegal_content_legal_ground, illegal_content_explanation
(3)(e) for incompatibility: the contractual ground and why the item is incompatible with it statement ground 2, ground_ref or the code's reference, explanation; the code's url decision_ground, incompatible_content_ground, incompatible_content_explanation, decision_ground_reference_url
(3)(f) the possibilities for redress policy redress; a complaint (§5) and its decision (§4.5) none: the database has no member for it; it is the statement to the affected participant's
the category of the decision (the database's, from Implementing Regulation (EU) 2024/2835 Annex II) the reason code's category category
the item's kind and date (the database's) the target content_type, content_date
the decision's date and identifier (the database's) the decision's t and digest application_date, puid

9.5 Categories

number category
1 STATEMENT_CATEGORY_ANIMAL_WELFARE
2 STATEMENT_CATEGORY_CONSUMER_INFORMATION
3 STATEMENT_CATEGORY_CYBER_VIOLENCE
4 STATEMENT_CATEGORY_CYBER_VIOLENCE_AGAINST_WOMEN
5 STATEMENT_CATEGORY_DATA_PROTECTION_AND_PRIVACY_VIOLATIONS
6 STATEMENT_CATEGORY_ILLEGAL_OR_HARMFUL_SPEECH
7 STATEMENT_CATEGORY_INTELLECTUAL_PROPERTY_INFRINGEMENTS
8 STATEMENT_CATEGORY_NEGATIVE_EFFECTS_ON_CIVIC_DISCOURSE_OR_ELECTIONS
9 STATEMENT_CATEGORY_NOT_SPECIFIED_NOTICE
10 STATEMENT_CATEGORY_OTHER_VIOLATION_TC
11 STATEMENT_CATEGORY_PROTECTION_OF_MINORS
12 STATEMENT_CATEGORY_RISK_FOR_PUBLIC_SECURITY
13 STATEMENT_CATEGORY_SCAMS_AND_FRAUD
14 STATEMENT_CATEGORY_SELF_HARM
15 STATEMENT_CATEGORY_UNSAFE_AND_PROHIBITED_PRODUCTS
16 STATEMENT_CATEGORY_VIOLENCE

These are the sixteen values the database accepts in this version, in its order. Category 9 is for a notice that does not specify the kind of illegal content (Implementing Regulation (EU) 2024/2835, Annex II).

9.6 Content types

target content_type content_type_other
chat, caption, poll_open, marker CONTENT_TYPE_TEXT
reaction CONTENT_TYPE_OTHER reaction
vote CONTENT_TYPE_OTHER poll vote
poll_close CONTENT_TYPE_OTHER poll tally
meta CONTENT_TYPE_OTHER timed metadata
cue CONTENT_TYPE_OTHER cue
an object of media 1 (audio) CONTENT_TYPE_AUDIO
an object of media 2 (video) or 3 (screen) CONTENT_TYPE_VIDEO

9.7 Personal data

Article 24(5) has the information submitted to the database contain no personal data. The export writes nothing from a principal's key, a notice's or a complaint's contact, statement or submitter, or the content of the item: its members come from the decision's verdict, t and digest, the target's kind and t, the policy's vocabulary and territory, and the statement's three texts. It never writes source_identity, which the database documentation reserves for the notifier's identity where strictly necessary. The statement's texts are copied as their author wrote them; whether they hold personal data is a question about their content that no verifier can answer, and they are written knowing that they are published.

9.8 When no record can be written

The record is written from the members in the order of §9.3. The first member that cannot be written as §9.3 has it names the refusal, sor_incomplete, stated with that member's name:

  • the decision has no statement, or an unknown one: decision_facts (and every member drawn from the statement after it);
  • a text that is empty, or longer than the member allows: that member;
  • the target is a withheld target or an unresolved one, so its kind and t are not held: content_type;
  • a date outside the range the member allows: that member;
  • a decision of basis 3 whose superseded decision's source cannot be written: source_type.

10. Reasons

The codes this extension adds, each with where it is raised:

reason raised at
policy_malformed §3.3
policy_unlinked §3.4
policy_conflicts_room §3.5
notice_malformed §5.2
statement_misplaced §7.2 step 4
statement_malformed §4.3, §7.2 step 4
reason_not_in_policy §7.4 step 1
target_unknown §7.4 step 2
target_not_decidable §4.1, §7.4 step 2
target_prev_mismatch §7.4 step 2
not_reviewer §4.4, §7.4 step 3
supersede_gap §4.5, §7.4 step 4
supersede_unknown §4.5, §7.4 step 4
supersede_mismatch §4.5, §7.4 step 4
notice_unknown §5.2, §7.4 step 5
notice_mismatch §5.2, §7.4 step 5
fallback_not_in_policy §4.6, §7.4 step 5
fallback_early §4.6, §7.4 step 5
sor_incomplete §9.8, with the member
redaction_not_applied §8.3

A moderation verifier also raises every code a room verifier raises (live-rooms §11 and the live-events codes it lists), at the room: kind_not_permitted among them for a decision by a principal without the moderate power (§4.4); truncated, malformed and unknown_version for a policy or a notice that does not read; wrong_room, bad_key and bad_signature for a policy or a notice of another room or whose signature fails; and delivery_gap for a gap that remains, which is not a refusal. A clip under moderation raises every code a clip verifier raises (live-recording §9), and redaction_not_applied.

11. Conformance

live-conformance/moderation/ holds the corpus, written from fixed seeds by wai_live_moderation_vectors gen: vectors.json, and each export as a file of its own, NAME.dK.sor.json, K the decision's position among the vector's decisions. It has:

  • Rooms, each a room (descriptor, grants, key-log head or none, chains), its policies, notices and objects, and the outcome §7.5 gives them with everything it states:
    • a chat room under a pre-moderation policy, replaced partway by a second policy that also reviews reactions after delivery: one message allowed before its deadline, one denied, one past its deadline taking the fallback, which the clock signs; a reaction reviewed after delivery whose fallback holds it;
    • the same room as a subscriber that is not a reviewer receives it, the denied message and the fallback's withheld, the gap they leave explained by the decisions;
    • a notice and its decision, and a trusted flagger's notice and its redaction;
    • an appeal: a complaint against a decision taken with automated means, and the decision that reverses it;
    • a room whose recorded take has a chunk redacted, and the same room after an appeal restores it;
    • one refused room for each reason of §10 a room raises, each a control of one defect, and a decision by a principal without the moderate power, which the room refuses kind_not_permitted;
    • a room with a gap that no decision explains.
  • Exports: every decision's record, byte for byte, none for an allow, and one refused sor_incomplete.
  • Clips under moderation over the redacted take: one that withholds the redacted chunk, one cut by bytes whose first chunk is the redacted one, one that presents the redacted chunk's bytes (redaction_not_applied), and, after the appeal, one that leaves the restored chunk out (clip_chunk_missing) and one that presents it.

A conforming verifier gives every room, export and clip the outcome, the reason, the place and everything else the corpus states. wai_live_moderation_vectors verify and the test tests/live_moderation.rs check that every file regenerates byte for byte, that wai-rs gives every case its outcome, that every reason of §10 appears, and that §10 lists exactly the reference's codes. live-conformance/live_moderation_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it verifies each room with the live-rooms corpus's second implementation (live_rooms_verify.py), each clip with the live-recording corpus's (live_record_verify.py), and every signature with WAI's strict rule (claim-conformance/_ed25519.py); it writes every export from §9 and requires its bytes; and it mutates copies of the corpus and requires every copy to fail.

12. Security and privacy considerations

  • Every decision is signed by its moderator, every statement rides in the moderator's chain, every policy is signed by the room authority, and every notice by its submitter. A relay can withhold any of them but alter none, and a withheld decision is a gap in its moderator's chain.
  • A relay holds the power to withhold. A subscriber checks it: an event withheld with no restricting decision behind it is a delivery gap that §7.3 does not explain, which the subscriber sees and can report. A relay that delivers a restricted item to a session is caught by the decision the session also holds.
  • Explaining a gap needs each restricted event's prev, which its moderator states. A moderator that states a false one makes the gap unexplained, never a forged event accepted: the chain still has to reach the event before the gap.
  • Withholding is not erasure. A denied item stays in the recording, and a redacted one stays wherever a copy was delivered before the decision; a redaction stops what WAI's relays, recordings and clips deliver from then on.
  • A decision names its target by digest only, and its statement holds no content. A notice holds its submitter's contact, which is personal data: a verifier is given notices to check decisions against them, and no export or derived record carries a notice's texts or its key.
  • The statement's texts are published with the decision and with its export. Whoever writes them writes them for a public record.
  • Fallback decisions are signed by the room's clock key, which is the relay's; a policy limits them to the fallback it names, at or after the deadline it names, so a relay cannot restrict an item for a reason the policy does not hold.
  • The automated field is its moderator's statement. Nothing in a record shows how a verdict was reached; the field states it, and an export carries it.