WAI Extension: Live Moderation
Mirrored from the canonical text at commit f871af55 ().
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 adecisionevent of live-events (§4.11 there), unchanged, followed by ametaevent 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 thelive_moderationmodule ofwai-rs(featurelive_moderation); the corpus islive-conformance/moderation/, and its second implementation islive-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 revisionfc3b1fd5(2026-09-23) of the documentation and revisionbf0afeb8(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.
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
decisionevent (live-events §4.11). Its statement is theWMS1payload of themetaevent 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:
scope1:codeis an event kind (live-events §4), and it must be a decidable kind (§4.1);scope2:codeis a media kind (live-rooms §6.1: 1 audio, 2 video, 3 screen), and the entry reviews every media object of that kind.mode1 pre: an item is held from delivery until it is decided or its deadline passes;mode2 post: an item is delivered at once and reviewed after.deadline_ms: how long after the item’sta 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 whenfallbackis allow.
A reason code is one entry of the room’s vocabulary:
code: the value a decision’sreason(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 anhttps://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:
- Fewer than 4 bytes:
truncated. A magic other thanWMP1:malformed. No byte after the magic:truncated. A version other than 1:unknown_version. - The bytes end before a field the layout requires:
truncated. - 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), withscope1 or 2,codeasscoperequires,mode1 or 2,deadline_msabove 0,fallback1, 2 or 3, andfallback_reason0 whenfallbackis 1 and a code ofreasonsotherwise; - the reason codes are in strictly ascending order of
code, each above 0, withground1 or 2,category1 to 16, andreferenceandurlas §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,markerorcaption. Acontrol, adecision, atake_commitand 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:
supersedeszero while the target already has a decision:supersede_gap;supersedesnaming 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
scope1 of the policy in effect at itstnames, and whose author is neither a room key nor a principal whose grant in effect at itsthas the moderate power; or - a media object the verifier is given (§7.1), whose media kind an entry of
scope2 of the policy in effect at itstnames.
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:
- when a decision on it has its
tat or below x: the verdict of the last such decision of its line, allowed, denied or redacted; - otherwise, an item with no entry: shown;
- otherwise, before its deadline: held in mode pre, pending in mode post;
- 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 thetthat 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:
- The room (live-rooms §9): a refusal is the moderation verifier’s, at
roomwith the room verifier’s own place. A gap is not a refusal: the steps go on, and §7.3 says which gaps decisions explain. - The policies, in the list’s order (§3.3, §3.4, §3.5): the refusal, at the policy’s index.
- The notices, in the list’s order (§5.2): the refusal, at the notice’s index.
- The statements. For each
metaevent the room accepted whose payload begins withWMS1, in the merged order: when itsseqis 0,statement_misplaced; when the verifier holds its principal’s event atseq− 1, that is a decision at the samet, otherwisestatement_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), otherwisestatement_malformed. At the statement’s record. - The gaps (§7.3): which gaps restricted items explain. No refusal.
- The decisions, in the merged order, each by §7.4: the refusal, at the decision’s record.
- 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:
- D is the
prevof the event at b + 1. - 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_type1 and a restricting verdict, otherwise the gap is not explained; seq s is withheld by that decision, and D becomes its statement’starget_prev. - 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:
- The reason. A policy is in effect at E’s
t, its vocabulary has E’sreason, and a known statement’sgroundis that code’s: otherwisereason_not_in_policy. - The target.
- Of type 2: an object the verifier is given has digest X and a
tat or below E’s: otherwisetarget_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, otherwisetarget_not_decidable; and a known statement’starget_previs itsprev, otherwisetarget_prev_mismatch. When X is a withheld target (§7.3), these hold. Otherwisetarget_unknown, unresolved while a gap remains.
- Of type 2: an object the verifier is given has digest X and a
- The reviewer (§4.4):
not_reviewer. Not made for a withheld target. - The line (§4.5):
supersede_gap,supersede_unknown(unresolved while a gap remains) orsupersede_mismatch. Not made for an unknown statement; with none,supersedesis zero. - 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:
- 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_inwhen its first chunk is not restricted andtrim_outwhen its last is not. - 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
tare 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
automatedfield is its moderator’s statement. Nothing in a record shows how a verdict was reached; the field states it, and an export carries it.