verify_clip is unchanged in behaviour (it exempts nothing); verify_clip_exempting passes over the chunks a caller names in step (k), which live-moderation §8.3 uses for chunks a standing decision restricts. The recordings corpus regenerates byte for byte. Co-Authored-By: Claude Opus 5.5 <[email protected]>
842 lines
51 KiB
Markdown
842 lines
51 KiB
Markdown
# 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](live-events.md) (§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/`](../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.
|
||
|
||
Relationship to the core spec: `wai.live.moderation` is a **record** ([SPEC](../SPEC.md) §5):
|
||
a signed record a WAI tool emits and verifies. It names no payload format and is not
|
||
dispatchable.
|
||
|
||
Relationship to [live-events](live-events.md): 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](timed-text.md)'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](live-rooms.md): 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](live-recording.md): 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](capture-binding.md) §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](jwp-receipts.md) §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](live-metering.md) §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](live-recording.md) §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/`](../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.
|