openvibers/openvibe.contracts Commits

Moderation audit events for ten more producers (ADR-022, WS-D task 1): common.moderation-action@1 (action verb id, target { type, id, owner_subject? }, actor_subject; optional reason and details; never the content) and tools, games, wiki, blog, news, reviews, deals, coupons, trade and codes .moderation.action as thin schemas over it; each manifest produces its type and Network consumes all ten; fixtures and checks (unreleased)

OP OpenVibers committed 26 Sept 2026 · 2d0a837207 · 56 files, +1063 −11


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex fef7c91c..f1bb4334 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,10 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## Unreleased
++
++**Moderation audit events for ten more producers** (ADR-022, roadmap WS-D task 1). **`common.moderation-action@1`** (owner network, public) is one staff or moderator action on someone else's content or account, modelled on `community.moderation.action`: `action` (a short verb id such as `post.hidden`), `target { type, id, owner_subject? }` and `actor_subject` are required; `reason` and `details` are optional; it never carries the content. Events requires an event's prefix to match its source, so each producer gets its own type, a thin schema over the shared one: **`tools.moderation.action`**, **`games.moderation.action`**, **`wiki.moderation.action`**, **`blog.moderation.action`**, **`news.moderation.action`**, **`reviews.moderation.action`**, **`deals.moderation.action`**, **`coupons.moderation.action`**, **`trade.moderation.action`** and **`codes.moderation.action`**. Each service manifest produces its type, and the network manifest consumes all ten into the moderation audit log. Fixtures for each; `npm test` checks that every one is the shared payload and that Network consumes every `*.moderation.action`. Additive.
++
## 0.52.0 — 2026-09-26
**`common.config-snapshot@1`** (roadmap WS-C task 7, the configuration model): one immutable revision of one service's configuration namespace (`live.site_settings`, `network.site_settings`, `media.storage_tier`), as `openvibe-shared/config` keeps it and `/api/admin/config` shows it. It carries service, namespace, a revision that grows per namespace and the previous one, a state (`proposed`, `active`, `superseded`, `rejected`, `rolled_back`), the values with every secret-class key as `{ redacted: true, fingerprint }` (an HMAC-SHA256 of the value under a secret key the service keeps per namespace: equal fingerprints within a namespace mean the value did not change, and the value cannot be recovered or guessed offline without the key), a `public`/`internal`/`secret` classification per key, a sha256 checksum over the canonical JSON of the values as shown (secrets as their markers, so anyone can check it and it reveals nothing more), who created and activated it and when, a reason, the rejection error, and `copied_from` for a rollback. A rejected revision names its error; an active, superseded or rolled-back one names when it became active. Fixtures for an active revision with a redacted secret, an imported first revision, a rejected tiering change, a rollback and a proposal; an unkeyed `sha256` marker is invalid. Additive.
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex e7228ecd..bd29abcd 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -190,6 +190,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "common.moderation-action",
++ "version": "1.0.0",
++ "owner": "network",
++ "schema": "common/moderation-action.v1.json",
++ "visibility": "public",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "media.file-upload",
"version": "1.0.0",
@@@ -322,6 +332,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "codes.moderation.action",
++ "version": "1.0.0",
++ "owner": "codes",
++ "schema": "events/payloads/codes.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "events.redaction-directive",
"version": "1.0.0",
@@@ -657,6 +677,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "wiki.moderation.action",
++ "version": "1.0.0",
++ "owner": "wiki",
++ "schema": "events/payloads/wiki.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "wiki.citation-attach-request",
"version": "1.0.0",
@@@ -945,6 +975,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "blog.moderation.action",
++ "version": "1.0.0",
++ "owner": "blog",
++ "schema": "events/payloads/blog.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "blog.blog-configure-request",
"version": "1.0.0",
@@@ -2159,6 +2199,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "tools.moderation.action",
++ "version": "1.0.0",
++ "owner": "tools",
++ "schema": "events/payloads/tools.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "live.stream.started",
"version": "1.0.0",
@@@ -2589,6 +2639,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "news.moderation.action",
++ "version": "1.0.0",
++ "owner": "news",
++ "schema": "events/payloads/news.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "news.cluster-audit",
"version": "1.0.0",
@@@ -2886,6 +2946,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "reviews.moderation.action",
++ "version": "1.0.0",
++ "owner": "reviews",
++ "schema": "events/payloads/reviews.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "reviews.correction-submit-request",
"version": "1.0.0",
@@@ -3165,6 +3235,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "coupons.moderation.action",
++ "version": "1.0.0",
++ "owner": "coupons",
++ "schema": "events/payloads/coupons.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "coupons.coupon",
"version": "1.0.0",
@@@ -3327,6 +3407,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "deals.moderation.action",
++ "version": "1.0.0",
++ "owner": "deals",
++ "schema": "events/payloads/deals.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "deals.flag-request",
"version": "1.0.0",
@@@ -3507,6 +3597,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "trade.moderation.action",
++ "version": "1.0.0",
++ "owner": "trade",
++ "schema": "events/payloads/trade.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "trade.alert-read-result",
"version": "1.0.0",
@@@ -3768,6 +3868,16 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "games.moderation.action",
++ "version": "1.0.0",
++ "owner": "games",
++ "schema": "events/payloads/games.moderation.action.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active",
++ "adr": "ADR-022"
++ },
{
"id": "games.announce-frame",
"version": "1.0.0",
ddiff --git a/contracts/common/moderation-action.v1.json b/contracts/common/moderation-action.v1.json
nnew file mode 100644
iindex 00000000..e68e4dc3
---- /dev/null
++++ b/contracts/common/moderation-action.v1.json
@@@ -0,0 +1,31 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/moderation-action.v1.json",
++ "title": "ModerationAction",
++ "description": "common.moderation-action@1 (ADR-022, roadmap WS-D task 1): one staff or moderator action on someone else's content or account, as a service reports it to OpenVibe.Network's moderation audit log. The payload of every <service>.moderation.action event that has no older shape of its own (tools, games, wiki, blog, news, reviews, deals, coupons, trade, codes); modelled on community.moderation.action@1. Written to the service's outbox in the transaction that performs the action. A person acting on their own content is not moderation and is not reported. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself, a secret or an address.",
++ "type": "object",
++ "required": ["action", "target", "actor_subject"],
++ "additionalProperties": false,
++ "properties": {
++ "action": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 64,
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$",
++ "description": "A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked)."
++ },
++ "target": {
++ "type": "object",
++ "required": ["type", "id"],
++ "additionalProperties": false,
++ "properties": {
++ "type": { "type": "string", "minLength": 1, "maxLength": 40, "pattern": "^[a-z][a-z0-9_]*$", "description": "What was acted on, in the service's own words (post, page, release, user, player, …)." },
++ "id": { "type": "string", "minLength": 1, "maxLength": 200 },
++ "owner_subject": { "type": ["string", "null"], "maxLength": 200, "description": "Whose content or account it was, when known." }
++ }
++ },
++ "actor_subject": { "type": ["string", "null"], "maxLength": 200, "description": "The staff member or moderator (null only for a service acting without a person)." },
++ "reason": { "type": ["string", "null"], "maxLength": 500 },
++ "details": { "type": "object", "description": "Per action: the fields changed, the previous and new state, a bulk action's count, … never the content." }
++ }
++}
ddiff --git a/contracts/events/payloads/blog.moderation.action.v1.json b/contracts/events/payloads/blog.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..ff746194
---- /dev/null
++++ b/contracts/events/payloads/blog.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.moderation.action.v1.json",
++ "title": "BlogModerationActionPayload",
++ "description": "blog.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Blog, such as editorial moderation of someone else's post or blog: hiding, unpublishing, deleting or restoring it, suspending a blog. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/codes.moderation.action.v1.json b/contracts/events/payloads/codes.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..ed494ad0
---- /dev/null
++++ b/contracts/events/payloads/codes.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/codes.moderation.action.v1.json",
++ "title": "CodesModerationActionPayload",
++ "description": "codes.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Codes, such as revoking or deprecating someone else's release or app, and withdrawing trust in a publisher or signing key. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/coupons.moderation.action.v1.json b/contracts/events/payloads/coupons.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..c8949d29
---- /dev/null
++++ b/contracts/events/payloads/coupons.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/coupons.moderation.action.v1.json",
++ "title": "CouponsModerationActionPayload",
++ "description": "coupons.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Coupons, such as approving, disabling or removing someone else's coupon, resolving reports, changing a merchant's status. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/deals.moderation.action.v1.json b/contracts/events/payloads/deals.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..47215cc7
---- /dev/null
++++ b/contracts/events/payloads/deals.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/deals.moderation.action.v1.json",
++ "title": "DealsModerationActionPayload",
++ "description": "deals.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Deals, such as resolving flags, hiding, removing or restoring someone else's offer or comment. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/games.moderation.action.v1.json b/contracts/events/payloads/games.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..fe15991d
---- /dev/null
++++ b/contracts/events/payloads/games.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/games.moderation.action.v1.json",
++ "title": "GamesModerationActionPayload",
++ "description": "games.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Games, such as kicking or banning a player, lifting a ban, and revoking or disabling someone else's mod. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/news.moderation.action.v1.json b/contracts/events/payloads/news.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..56a875da
---- /dev/null
++++ b/contracts/events/payloads/news.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/news.moderation.action.v1.json",
++ "title": "NewsModerationActionPayload",
++ "description": "news.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.News, such as editorial moderation: unpublishing, retracting or restoring a story, resolving flags, blocking a source. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/reviews.moderation.action.v1.json b/contracts/events/payloads/reviews.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..a7d07651
---- /dev/null
++++ b/contracts/events/payloads/reviews.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/reviews.moderation.action.v1.json",
++ "title": "ReviewsModerationActionPayload",
++ "description": "reviews.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Reviews, such as editorial moderation: removing or restoring someone else's signal or review, unpublishing a summary, merging or splitting entities. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/tools.moderation.action.v1.json b/contracts/events/payloads/tools.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..5ab5e497
---- /dev/null
++++ b/contracts/events/payloads/tools.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/tools.moderation.action.v1.json",
++ "title": "ToolsModerationActionPayload",
++ "description": "tools.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Tools (apps/gateway and its guard), such as guard blocks and unblocks of an account, an app or an address, and staff actions on someone else's jobs or tool pages. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/trade.moderation.action.v1.json b/contracts/events/payloads/trade.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..baf79566
---- /dev/null
++++ b/contracts/events/payloads/trade.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/trade.moderation.action.v1.json",
++ "title": "TradeModerationActionPayload",
++ "description": "trade.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Trade, such as hiding, removing or restoring someone else's listing or observation. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/contracts/events/payloads/wiki.moderation.action.v1.json b/contracts/events/payloads/wiki.moderation.action.v1.json
nnew file mode 100644
iindex 00000000..bc930b86
---- /dev/null
++++ b/contracts/events/payloads/wiki.moderation.action.v1.json
@@@ -0,0 +1,7 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.moderation.action.v1.json",
++ "title": "WikiModerationActionPayload",
++ "description": "wiki.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Wiki, such as editorial moderation of someone else's page or revision: hiding, unpublishing, deleting, restoring, locking or reverting. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++}
ddiff --git a/fixtures/blog.moderation.action/invalid/no-target.json b/fixtures/blog.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..7846b213
---- /dev/null
++++ b/fixtures/blog.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "post.unpublished",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "doxxing"
++}
ddiff --git a/fixtures/blog.moderation.action/valid/post-unpublished.json b/fixtures/blog.moderation.action/valid/post-unpublished.json
nnew file mode 100644
iindex 00000000..a2f880b6
---- /dev/null
++++ b/fixtures/blog.moderation.action/valid/post-unpublished.json
@@@ -0,0 +1,13 @@
++{
++ "action": "post.unpublished",
++ "target": {
++ "type": "post",
++ "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "doxxing",
++ "details": {
++ "previous": "published"
++ }
++}
ddiff --git a/fixtures/codes.moderation.action/invalid/no-target.json b/fixtures/codes.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..d80cf52d
---- /dev/null
++++ b/fixtures/codes.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "release.revoked",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "malware in 1.4.2"
++}
ddiff --git a/fixtures/codes.moderation.action/valid/release-revoked.json b/fixtures/codes.moderation.action/valid/release-revoked.json
nnew file mode 100644
iindex 00000000..3cd99738
---- /dev/null
++++ b/fixtures/codes.moderation.action/valid/release-revoked.json
@@@ -0,0 +1,14 @@
++{
++ "action": "release.revoked",
++ "target": {
++ "type": "release",
++ "id": "rel_01JAB2C3D4E5F6G7H8J9K0MNPT",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "malware in 1.4.2",
++ "details": {
++ "app": "app_3",
++ "version": "1.4.2"
++ }
++}
ddiff --git a/fixtures/common.moderation-action/invalid/action-not-an-id.json b/fixtures/common.moderation-action/invalid/action-not-an-id.json
nnew file mode 100644
iindex 00000000..28d6ad69
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/action-not-an-id.json
@@@ -0,0 +1,8 @@
++{
++ "action": "Hid The Post",
++ "target": {
++ "type": "post",
++ "id": "x"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/common.moderation-action/invalid/carries-content.json b/fixtures/common.moderation-action/invalid/carries-content.json
nnew file mode 100644
iindex 00000000..b9ef28d9
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/carries-content.json
@@@ -0,0 +1,9 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post",
++ "id": "x"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "content": "the post text"
++}
ddiff --git a/fixtures/common.moderation-action/invalid/no-actor.json b/fixtures/common.moderation-action/invalid/no-actor.json
nnew file mode 100644
iindex 00000000..066bcba5
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/no-actor.json
@@@ -0,0 +1,7 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post",
++ "id": "x"
++ }
++}
ddiff --git a/fixtures/common.moderation-action/invalid/reason-too-long.json b/fixtures/common.moderation-action/invalid/reason-too-long.json
nnew file mode 100644
iindex 00000000..bc3fee6b
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/reason-too-long.json
@@@ -0,0 +1,9 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post",
++ "id": "x"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
++}
ddiff --git a/fixtures/common.moderation-action/invalid/target-extra-field.json b/fixtures/common.moderation-action/invalid/target-extra-field.json
nnew file mode 100644
iindex 00000000..743a7492
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/target-extra-field.json
@@@ -0,0 +1,9 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post",
++ "id": "x",
++ "title": "Hello"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/common.moderation-action/invalid/target-without-id.json b/fixtures/common.moderation-action/invalid/target-without-id.json
nnew file mode 100644
iindex 00000000..12942bed
---- /dev/null
++++ b/fixtures/common.moderation-action/invalid/target-without-id.json
@@@ -0,0 +1,7 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/common.moderation-action/valid/minimal.json b/fixtures/common.moderation-action/valid/minimal.json
nnew file mode 100644
iindex 00000000..bb487fe3
---- /dev/null
++++ b/fixtures/common.moderation-action/valid/minimal.json
@@@ -0,0 +1,8 @@
++{
++ "action": "user.banned",
++ "target": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ"
++}
ddiff --git a/fixtures/common.moderation-action/valid/post-hidden.json b/fixtures/common.moderation-action/valid/post-hidden.json
nnew file mode 100644
iindex 00000000..e9208256
---- /dev/null
++++ b/fixtures/common.moderation-action/valid/post-hidden.json
@@@ -0,0 +1,14 @@
++{
++ "action": "post.hidden",
++ "target": {
++ "type": "post",
++ "id": "pst_01JAB2C3D4E5F6G7H8J9K0MNPS",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "spam",
++ "details": {
++ "previous": "published",
++ "now": "hidden"
++ }
++}
ddiff --git a/fixtures/common.moderation-action/valid/service-actor.json b/fixtures/common.moderation-action/valid/service-actor.json
nnew file mode 100644
iindex 00000000..ea0fc171
---- /dev/null
++++ b/fixtures/common.moderation-action/valid/service-actor.json
@@@ -0,0 +1,12 @@
++{
++ "action": "guard.blocked",
++ "target": {
++ "type": "address",
++ "id": "ip:3f9a1c"
++ },
++ "actor_subject": null,
++ "reason": null,
++ "details": {
++ "rule": "rate"
++ }
++}
ddiff --git a/fixtures/coupons.moderation.action/invalid/no-target.json b/fixtures/coupons.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..34d6eaf9
---- /dev/null
++++ b/fixtures/coupons.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "coupon.disabled",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": null
++}
ddiff --git a/fixtures/coupons.moderation.action/valid/coupon-disabled.json b/fixtures/coupons.moderation.action/valid/coupon-disabled.json
nnew file mode 100644
iindex 00000000..e3f3ea6d
---- /dev/null
++++ b/fixtures/coupons.moderation.action/valid/coupon-disabled.json
@@@ -0,0 +1,13 @@
++{
++ "action": "coupon.disabled",
++ "target": {
++ "type": "coupon",
++ "id": "cpn_12",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": null,
++ "details": {
++ "reports": 4
++ }
++}
ddiff --git a/fixtures/deals.moderation.action/invalid/no-target.json b/fixtures/deals.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..3b195ba2
---- /dev/null
++++ b/fixtures/deals.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "offer.hidden",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "expired code reposted"
++}
ddiff --git a/fixtures/deals.moderation.action/valid/offer-hidden.json b/fixtures/deals.moderation.action/valid/offer-hidden.json
nnew file mode 100644
iindex 00000000..bae905b7
---- /dev/null
++++ b/fixtures/deals.moderation.action/valid/offer-hidden.json
@@@ -0,0 +1,13 @@
++{
++ "action": "offer.hidden",
++ "target": {
++ "type": "offer",
++ "id": "off_5",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "expired code reposted",
++ "details": {
++ "flags_resolved": 3
++ }
++}
ddiff --git a/fixtures/games.moderation.action/invalid/no-target.json b/fixtures/games.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..dbf7ac7d
---- /dev/null
++++ b/fixtures/games.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "player.banned",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "griefing"
++}
ddiff --git a/fixtures/games.moderation.action/valid/player-banned.json b/fixtures/games.moderation.action/valid/player-banned.json
nnew file mode 100644
iindex 00000000..c988f3cf
---- /dev/null
++++ b/fixtures/games.moderation.action/valid/player-banned.json
@@@ -0,0 +1,14 @@
++{
++ "action": "player.banned",
++ "target": {
++ "type": "player",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "griefing",
++ "details": {
++ "world": "wld_1",
++ "duration_s": 86400
++ }
++}
ddiff --git a/fixtures/news.moderation.action/invalid/no-target.json b/fixtures/news.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..c499bcdd
---- /dev/null
++++ b/fixtures/news.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "story.retracted",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "fabricated source"
++}
ddiff --git a/fixtures/news.moderation.action/valid/story-retracted.json b/fixtures/news.moderation.action/valid/story-retracted.json
nnew file mode 100644
iindex 00000000..bf0e2432
---- /dev/null
++++ b/fixtures/news.moderation.action/valid/story-retracted.json
@@@ -0,0 +1,11 @@
++{
++ "action": "story.retracted",
++ "target": {
++ "type": "story",
++ "id": "sty_17",
++ "owner_subject": null
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "fabricated source",
++ "details": {}
++}
ddiff --git a/fixtures/reviews.moderation.action/invalid/no-target.json b/fixtures/reviews.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..1550da52
---- /dev/null
++++ b/fixtures/reviews.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "signal.removed",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "fake review"
++}
ddiff --git a/fixtures/reviews.moderation.action/valid/signal-removed.json b/fixtures/reviews.moderation.action/valid/signal-removed.json
nnew file mode 100644
iindex 00000000..a3fb83b2
---- /dev/null
++++ b/fixtures/reviews.moderation.action/valid/signal-removed.json
@@@ -0,0 +1,13 @@
++{
++ "action": "signal.removed",
++ "target": {
++ "type": "signal",
++ "id": "sig_9",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "fake review",
++ "details": {
++ "entity": "ent_3"
++ }
++}
ddiff --git a/fixtures/tools.moderation.action/invalid/no-target.json b/fixtures/tools.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..98c40c9d
---- /dev/null
++++ b/fixtures/tools.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "guard.blocked",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "scripted abuse of the image tools"
++}
ddiff --git a/fixtures/tools.moderation.action/valid/guard-blocked.json b/fixtures/tools.moderation.action/valid/guard-blocked.json
nnew file mode 100644
iindex 00000000..120c1f3b
---- /dev/null
++++ b/fixtures/tools.moderation.action/valid/guard-blocked.json
@@@ -0,0 +1,14 @@
++{
++ "action": "guard.blocked",
++ "target": {
++ "type": "user",
++ "id": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "scripted abuse of the image tools",
++ "details": {
++ "scope": "account",
++ "expires_at": "2026-10-02T00:00:00Z"
++ }
++}
ddiff --git a/fixtures/trade.moderation.action/invalid/no-target.json b/fixtures/trade.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..17362055
---- /dev/null
++++ b/fixtures/trade.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "listing.removed",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "counterfeit"
++}
ddiff --git a/fixtures/trade.moderation.action/valid/listing-removed.json b/fixtures/trade.moderation.action/valid/listing-removed.json
nnew file mode 100644
iindex 00000000..d0205a0b
---- /dev/null
++++ b/fixtures/trade.moderation.action/valid/listing-removed.json
@@@ -0,0 +1,11 @@
++{
++ "action": "listing.removed",
++ "target": {
++ "type": "listing",
++ "id": "lst_8",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "counterfeit",
++ "details": {}
++}
ddiff --git a/fixtures/wiki.moderation.action/invalid/no-target.json b/fixtures/wiki.moderation.action/invalid/no-target.json
nnew file mode 100644
iindex 00000000..31a02966
---- /dev/null
++++ b/fixtures/wiki.moderation.action/invalid/no-target.json
@@@ -0,0 +1,5 @@
++{
++ "action": "page.locked",
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "edit war"
++}
ddiff --git a/fixtures/wiki.moderation.action/valid/page-locked.json b/fixtures/wiki.moderation.action/valid/page-locked.json
nnew file mode 100644
iindex 00000000..1282a181
---- /dev/null
++++ b/fixtures/wiki.moderation.action/valid/page-locked.json
@@@ -0,0 +1,11 @@
++{
++ "action": "page.locked",
++ "target": {
++ "type": "page",
++ "id": "pg_42",
++ "owner_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPR"
++ },
++ "actor_subject": "usr_01JAB2C3D4E5F6G7H8J9K0MNPQ",
++ "reason": "edit war",
++ "details": {}
++}
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex 1ddb3078..a8087b7c 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -1995,6 +1995,82 @@
]
}
},
++ "common.moderation-action": {
++ "version": "1.0.0",
++ "owner": "network",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/common/moderation-action.v1.json",
++ "title": "ModerationAction",
++ "description": "common.moderation-action@1 (ADR-022, roadmap WS-D task 1): one staff or moderator action on someone else's content or account, as a service reports it to OpenVibe.Network's moderation audit log. The payload of every <service>.moderation.action event that has no older shape of its own (tools, games, wiki, blog, news, reviews, deals, coupons, trade, codes); modelled on community.moderation.action@1. Written to the service's outbox in the transaction that performs the action. A person acting on their own content is not moderation and is not reported. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself, a secret or an address.",
++ "type": "object",
++ "required": [
++ "action",
++ "target",
++ "actor_subject"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "action": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 64,
++ "pattern": "^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$",
++ "description": "A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked)."
++ },
++ "target": {
++ "type": "object",
++ "required": [
++ "type",
++ "id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "type": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 40,
++ "pattern": "^[a-z][a-z0-9_]*$",
++ "description": "What was acted on, in the service's own words (post, page, release, user, player, …)."
++ },
++ "id": {
++ "type": "string",
++ "minLength": 1,
++ "maxLength": 200
++ },
++ "owner_subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "Whose content or account it was, when known."
++ }
++ }
++ },
++ "actor_subject": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 200,
++ "description": "The staff member or moderator (null only for a service acting without a person)."
++ },
++ "reason": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "maxLength": 500
++ },
++ "details": {
++ "type": "object",
++ "description": "Per action: the fields changed, the previous and new state, a bulk action's count, … never the content."
++ }
++ }
++ }
++ },
"media.file-upload": {
"version": "1.0.0",
"owner": "media",
@@@ -4028,6 +4104,18 @@
]
}
},
++ "codes.moderation.action": {
++ "version": "1.0.0",
++ "owner": "codes",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/codes.moderation.action.v1.json",
++ "title": "CodesModerationActionPayload",
++ "description": "codes.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Codes, such as revoking or deprecating someone else's release or app, and withdrawing trust in a publisher or signing key. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"events.redaction-directive": {
"version": "1.0.0",
"owner": "events",
@@@ -6934,6 +7022,18 @@
}
}
},
++ "wiki.moderation.action": {
++ "version": "1.0.0",
++ "owner": "wiki",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/wiki.moderation.action.v1.json",
++ "title": "WikiModerationActionPayload",
++ "description": "wiki.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Wiki, such as editorial moderation of someone else's page or revision: hiding, unpublishing, deleting, restoring, locking or reverting. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"wiki.citation-attach-request": {
"version": "1.0.0",
"owner": "wiki",
@@@ -9503,6 +9603,18 @@
}
}
},
++ "blog.moderation.action": {
++ "version": "1.0.0",
++ "owner": "blog",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/blog.moderation.action.v1.json",
++ "title": "BlogModerationActionPayload",
++ "description": "blog.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Blog, such as editorial moderation of someone else's post or blog: hiding, unpublishing, deleting or restoring it, suspending a blog. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"blog.blog-configure-request": {
"version": "1.0.0",
"owner": "blog",
@@@ -22914,6 +23026,18 @@
}
}
},
++ "tools.moderation.action": {
++ "version": "1.0.0",
++ "owner": "tools",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/tools.moderation.action.v1.json",
++ "title": "ToolsModerationActionPayload",
++ "description": "tools.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Tools (apps/gateway and its guard), such as guard blocks and unblocks of an account, an app or an address, and staff actions on someone else's jobs or tool pages. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"live.stream.started": {
"version": "1.0.0",
"owner": "live",
@@@ -27577,6 +27701,18 @@
}
}
},
++ "news.moderation.action": {
++ "version": "1.0.0",
++ "owner": "news",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/news.moderation.action.v1.json",
++ "title": "NewsModerationActionPayload",
++ "description": "news.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.News, such as editorial moderation: unpublishing, retracting or restoring a story, resolving flags, blocking a source. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"news.cluster-audit": {
"version": "1.0.0",
"owner": "news",
@@@ -31586,6 +31722,18 @@
}
}
},
++ "reviews.moderation.action": {
++ "version": "1.0.0",
++ "owner": "reviews",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/reviews.moderation.action.v1.json",
++ "title": "ReviewsModerationActionPayload",
++ "description": "reviews.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Reviews, such as editorial moderation: removing or restoring someone else's signal or review, unpublishing a summary, merging or splitting entities. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"reviews.correction-submit-request": {
"version": "1.0.0",
"owner": "reviews",
@@@ -36392,6 +36540,18 @@
}
}
},
++ "coupons.moderation.action": {
++ "version": "1.0.0",
++ "owner": "coupons",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/coupons.moderation.action.v1.json",
++ "title": "CouponsModerationActionPayload",
++ "description": "coupons.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Coupons, such as approving, disabling or removing someone else's coupon, resolving reports, changing a merchant's status. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"coupons.coupon": {
"version": "1.0.0",
"owner": "coupons",
@@@ -38010,6 +38170,18 @@
}
}
},
++ "deals.moderation.action": {
++ "version": "1.0.0",
++ "owner": "deals",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/deals.moderation.action.v1.json",
++ "title": "DealsModerationActionPayload",
++ "description": "deals.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Deals, such as resolving flags, hiding, removing or restoring someone else's offer or comment. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"deals.flag-request": {
"version": "1.0.0",
"owner": "deals",
@@@ -40264,6 +40436,18 @@
}
}
},
++ "trade.moderation.action": {
++ "version": "1.0.0",
++ "owner": "trade",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/trade.moderation.action.v1.json",
++ "title": "TradeModerationActionPayload",
++ "description": "trade.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Trade, such as hiding, removing or restoring someone else's listing or observation. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"trade.alert-read-result": {
"version": "1.0.0",
"owner": "trade",
@@@ -42777,6 +42961,18 @@
}
}
},
++ "games.moderation.action": {
++ "version": "1.0.0",
++ "owner": "games",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/events/payloads/games.moderation.action.v1.json",
++ "title": "GamesModerationActionPayload",
++ "description": "games.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Games, such as kicking or banning a player, lifting a ban, and revoking or disabling someone else's mod. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.",
++ "$ref": "../../common/moderation-action.v1.json"
++ }
++ },
"games.announce-frame": {
"version": "1.0.0",
"owner": "games",
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 8f9c9acb..7be6980d 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -736,6 +736,37 @@ export type ConfigSnapshot = {
copied_from?: number | null;
};
++/** [email protected] (owner: network) */
++/**
++ * common.moderation-action@1 (ADR-022, roadmap WS-D task 1): one staff or moderator action on someone else's content or account, as a service reports it to OpenVibe.Network's moderation audit log. The payload of every <service>.moderation.action event that has no older shape of its own (tools, games, wiki, blog, news, reviews, deals, coupons, trade, codes); modelled on community.moderation.action@1. Written to the service's outbox in the transaction that performs the action. A person acting on their own content is not moderation and is not reported. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself, a secret or an address.
++ */
++export interface ModerationAction {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: media) */
/**
* media.file-upload@1: the multipart/form-data body of POST /api/v1/:app/files (media.object.upload, namespace :app): one part named `file` with the bytes and its filename and type. The tenant's quota is checked against the bytes; the answer is media.file@1 (201).
@@@ -5969,6 +6000,37 @@ export type CodesReleaseManageRequest =
}
| NoBody;
++/** [email protected] (owner: codes) */
++/**
++ * codes.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Codes, such as revoking or deprecating someone else's release or app, and withdrawing trust in a publisher or signing key. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface CodesModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: events) */
/**
* payload.redacts (ADR-026): a producer takes back events it published earlier. Any event may carry it, normally the producer's own *.deleted event. It names event_ids, or subject_type + subject_ids, or both, never an empty list. event_ids names events directly; subject_type + subject_ids names every stored event of the same source (for an app, the same project and environment) about those subjects. OpenVibe.Events rewrites each target into a tombstone (events.tombstone-payload@1) in the transaction that stores the directive. Naming another source's event is 403 events.redaction_not_allowed (the whole batch is refused); a malformed directive is 422 events.invalid_redaction. An event that carries a directive is never redacted itself.
@@@ -12458,6 +12520,37 @@ export interface WikiIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: wiki) */
++/**
++ * wiki.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Wiki, such as editorial moderation of someone else's page or revision: hiding, unpublishing, deleting, restoring, locking or reverting. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface WikiModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: wiki) */
/**
* wiki.citation-attach-request@1: the body of POST /api/v1/pages/:id/revisions/:n/citations on OpenVibe.Wiki (wiki.citation.attach, editors): sources for the newest revision while it is unpublished (a published revision's sources are fixed: 409 citation.revision_published; an older one is 409 citation.not_head). A URL citation needs retrieved_at; a Sources item id brings its own URL, title and retrieval time.
@@@ -13786,6 +13879,37 @@ export interface BlogIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: blog) */
++/**
++ * blog.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Blog, such as editorial moderation of someone else's post or blog: hiding, unpublishing, deleting or restoring it, suspending a blog. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface BlogModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: blog) */
/**
* blog.blog-configure-request@1: the body of PATCH /api/v1/blogs/:handle on OpenVibe.Blog (blog.blog.configure; an owner): title (at most 120 characters, not blank), description (500), language (a BCP 47 tag) and feeds { rss, atom, json, item_count (1-50), full_content }. Fields left out keep their value.
@@@ -24433,6 +24557,37 @@ export interface ToolsJobFailedPayload {
retryable: boolean;
}
++/** [email protected] (owner: tools) */
++/**
++ * tools.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Tools (apps/gateway and its guard), such as guard blocks and unblocks of an account, an app or an address, and staff actions on someone else's jobs or tool pages. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface ToolsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: live) */
/**
* live.stream.started v1 (OpenVibe.Live server/events/stream-events.js envelopeFor, fired by server/db/database.js createStream). A streams row went live: WebRTC/JSMPEG from the dashboard, RTMP ingest, WHIP, or an OpenRe.Stream session mirrored into Live. Written to Live's event_outbox in the transaction that inserts the row, so the event exists if and only if the stream did. Carries public channel facts only (the stream is listed publicly already); consumers such as Network's go-live notifications decide who hears about it. Never the stream key, the Live user id or the description. Envelope: subject { type: stream, id: <stream_id as a string>, revision: 1 }, visibility public, priority important, actor the streamer's user subject when Live knows it, else service:live.
@@@ -26852,6 +27007,37 @@ export interface NewsIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: news) */
++/**
++ * news.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.News, such as editorial moderation: unpublishing, retracting or restoring a story, resolving flags, blocking a source. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface NewsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: news) */
/**
* news.cluster-audit@1: one audited cluster change on OpenVibe.News (server/domain/clusters.js shapeAudit): a merge, a split or the reversal of one, with exactly which source items moved, who did it and why. Merge: cluster_id survived and other_id was absorbed; split: other_id is the new cluster.
@@@ -29470,6 +29656,37 @@ export interface ReviewsIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: reviews) */
++/**
++ * reviews.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Reviews, such as editorial moderation: removing or restoring someone else's signal or review, unpublishing a summary, merging or splitting entities. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface ReviewsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: reviews) */
/**
* reviews.correction-submit-request@1: the body of reviews.correction.submit on OpenVibe.Reviews: POST /api/v1/entities/:ref/corrections { body, target_type?, target_id?, evidence_url? } sends a correction to the editors' queue for the person named in X-OV-Subject (a signed-in person: 403 reviews.person_required). Refusals: 422 correction.invalid (unknown target_type, body under 10 or over 4000 characters, evidence_url not http(s)), 409 correction.duplicate (the same open request again), 429 correction.rate_limited (20 a day per person), 404 entity.not_found, 410 entity.deleted.
@@@ -35707,6 +35924,37 @@ export interface CouponsIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: coupons) */
++/**
++ * coupons.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Coupons, such as approving, disabling or removing someone else's coupon, resolving reports, changing a merchant's status. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface CouponsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: coupons) */
/**
* coupons.coupon@1: one code as OpenVibe.Coupons shows it (server/domain/coupons.js view): its status and confidence come only from people's reports (counts in a window, report times rounded to the hour), a known or unknown expiry, restrictions, hints and evidence. No submitter or reporter is ever named.
@@@ -36715,6 +36963,37 @@ export interface DealsIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: deals) */
++/**
++ * deals.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Deals, such as resolving flags, hiding, removing or restoring someone else's offer or comment. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface DealsModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: deals) */
/**
* deals.flag-request@1: the body of POST /api/v1/offers/:id/flags on OpenVibe.Deals (deals.flag.create; a person): { kind, reason?, duplicate_of? } — duplicate_of (the other deal, id, slug or /d/ link) is required for kind duplicate. An open flag of the same kind by the same person is refreshed, not repeated.
@@@ -37326,6 +37605,37 @@ export interface TradeIndexDocumentDeletedPayload {
revision: number;
}
++/** [email protected] (owner: trade) */
++/**
++ * trade.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Trade, such as hiding, removing or restoring someone else's listing or observation. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface TradeModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: trade) */
/**
* trade.alert-read-result@1: answers of trade.alert.read on OpenVibe.Trade (the acting person's): GET /api/v1/alerts → { rules }; GET /api/v1/alerts/deliveries?limit= → { deliveries } (newest first, at most 200: which rule fired on what, and the event sent).
@@@ -38317,6 +38627,37 @@ export interface GamesModRevokedPayload {
reason?: string;
}
++/** [email protected] (owner: games) */
++/**
++ * games.moderation.action v1 (ADR-022, roadmap WS-D task 1). A staff or moderator action on someone else's content or account in OpenVibe.Games, such as kicking or banning a player, lifting a ban, and revoking or disabling someone else's mod. Not a person acting on their own content. Written to the service's outbox in the transaction that performs the action; OpenVibe.Network keeps it in the moderation audit log. The payload is common.moderation-action@1. Envelope: subject { type: moderation_action, id: <target type>:<target id> }, visibility internal, actor the staff member. Never carries the content itself.
++ */
++export interface GamesModerationActionPayload {
++ /**
++ * A short verb id, as the service names it: <thing>.<what happened> (post.hidden, user.banned, release.revoked, guard.blocked).
++ */
++ action: string;
++ target: {
++ /**
++ * What was acted on, in the service's own words (post, page, release, user, player, …).
++ */
++ type: string;
++ id: string;
++ /**
++ * Whose content or account it was, when known.
++ */
++ owner_subject?: string | null;
++ };
++ /**
++ * The staff member or moderator (null only for a service acting without a person).
++ */
++ actor_subject: string | null;
++ reason?: string | null;
++ /**
++ * Per action: the fields changed, the previous and new state, a bulk action's count, … never the content.
++ */
++ details?: {};
++}
++
/** [email protected] (owner: games) */
/**
* games.announce-frame@1: the frame OpenVibe.Games broadcasts to every connected player for an announcement (gameServer broadcastAll): { t: announce, text }. A mod's announcement text is prefixed with its name in brackets ([Mod name] text); the binding itself returns nothing.
ddiff --git a/manifests/services/blog.json b/manifests/services/blog.json
iindex aa21a554..18ac0083 100644
---- a/manifests/services/blog.json
++++ b/manifests/services/blog.json
@@@ -48,7 +48,8 @@
"blog.post.deleted",
"blog.schedule.failed",
"blog.index_document.upserted",
-- "blog.index_document.deleted"
++ "blog.index_document.deleted",
++ "blog.moderation.action"
],
"eventsConsumed": [],
"namespacesOwned": [
ddiff --git a/manifests/services/codes.json b/manifests/services/codes.json
iindex ee9f08a5..92558664 100644
---- a/manifests/services/codes.json
++++ b/manifests/services/codes.json
@@@ -33,7 +33,8 @@
"eventsProduced": [
"codes.app.published",
"codes.app.deprecated",
-- "codes.app.revoked"
++ "codes.app.revoked",
++ "codes.moderation.action"
],
"eventsConsumed": [],
"namespacesOwned": [
ddiff --git a/manifests/services/coupons.json b/manifests/services/coupons.json
iindex da16e4b0..d622c4ad 100644
---- a/manifests/services/coupons.json
++++ b/manifests/services/coupons.json
@@@ -43,7 +43,8 @@
"coupons.report.created",
"coupons.confidence.changed",
"coupons.index_document.upserted",
-- "coupons.index_document.deleted"
++ "coupons.index_document.deleted",
++ "coupons.moderation.action"
],
"eventsConsumed": [],
"namespacesOwned": [
ddiff --git a/manifests/services/deals.json b/manifests/services/deals.json
iindex 21b8a426..626182bc 100644
---- a/manifests/services/deals.json
++++ b/manifests/services/deals.json
@@@ -48,7 +48,8 @@
"deals.vote.changed",
"deals.watch.matched",
"deals.index_document.upserted",
-- "deals.index_document.deleted"
++ "deals.index_document.deleted",
++ "deals.moderation.action"
],
"eventsConsumed": [
"sources.item.created",
ddiff --git a/manifests/services/games.json b/manifests/services/games.json
iindex dfd59581..47eea7f1 100644
---- a/manifests/services/games.json
++++ b/manifests/services/games.json
@@@ -43,7 +43,8 @@
"games.mod.enabled",
"games.mod.disabled",
"games.mod.grants_changed",
-- "games.mod.revoked"
++ "games.mod.revoked",
++ "games.moderation.action"
],
"eventsConsumed": [
"network.user.token_valid_after"
ddiff --git a/manifests/services/network.json b/manifests/services/network.json
iindex 9a5e52b5..e357219f 100644
---- a/manifests/services/network.json
++++ b/manifests/services/network.json
@@@ -60,13 +60,23 @@
],
"eventsConsumed": [
"billing.staff.action",
++ "blog.moderation.action",
"chat.moderation.action",
++ "codes.moderation.action",
"community.moderation.action",
++ "coupons.moderation.action",
++ "deals.moderation.action",
"deals.watch.matched",
++ "games.moderation.action",
"live.moderation.action",
"live.stream.started",
++ "news.moderation.action",
++ "reviews.moderation.action",
"tips.interaction.moderated",
-- "trade.alert.triggered"
++ "tools.moderation.action",
++ "trade.alert.triggered",
++ "trade.moderation.action",
++ "wiki.moderation.action"
],
"namespacesOwned": [
"identity.*",
ddiff --git a/manifests/services/news.json b/manifests/services/news.json
iindex 9e35f54d..23ed2621 100644
---- a/manifests/services/news.json
++++ b/manifests/services/news.json
@@@ -51,7 +51,8 @@
"news.story.unpublished",
"news.story.retracted",
"news.index_document.upserted",
-- "news.index_document.deleted"
++ "news.index_document.deleted",
++ "news.moderation.action"
],
"eventsConsumed": [
"sources.item.created",
ddiff --git a/manifests/services/reviews.json b/manifests/services/reviews.json
iindex 5f623e7d..4faa0ee3 100644
---- a/manifests/services/reviews.json
++++ b/manifests/services/reviews.json
@@@ -46,7 +46,8 @@
"reviews.summary.updated",
"reviews.summary.unpublished",
"reviews.index_document.upserted",
-- "reviews.index_document.deleted"
++ "reviews.index_document.deleted",
++ "reviews.moderation.action"
],
"eventsConsumed": [
"sources.item.created",
ddiff --git a/manifests/services/tools.json b/manifests/services/tools.json
iindex b9d0f5f7..adffc5ec 100644
---- a/manifests/services/tools.json
++++ b/manifests/services/tools.json
@@@ -38,7 +38,8 @@
"tools.job.created",
"tools.job.started",
"tools.job.succeeded",
-- "tools.job.failed"
++ "tools.job.failed",
++ "tools.moderation.action"
],
"eventsConsumed": [
"network.user.token_valid_after"
ddiff --git a/manifests/services/trade.json b/manifests/services/trade.json
iindex 7d02ce80..317acc0c 100644
---- a/manifests/services/trade.json
++++ b/manifests/services/trade.json
@@@ -47,7 +47,8 @@
"trade.source.stale",
"trade.source.recovered",
"trade.index_document.upserted",
-- "trade.index_document.deleted"
++ "trade.index_document.deleted",
++ "trade.moderation.action"
],
"eventsConsumed": [
"sources.item.created",
ddiff --git a/manifests/services/wiki.json b/manifests/services/wiki.json
iindex bfd5975a..9271938a 100644
---- a/manifests/services/wiki.json
++++ b/manifests/services/wiki.json
@@@ -45,7 +45,8 @@
"wiki.page.deleted",
"wiki.watch.triggered",
"wiki.index_document.upserted",
-- "wiki.index_document.deleted"
++ "wiki.index_document.deleted",
++ "wiki.moderation.action"
],
"eventsConsumed": [],
"namespacesOwned": [
ddiff --git a/test/run.js b/test/run.js
iindex 05d119f1..50d8d4ea 100644
---- a/test/run.js
++++ b/test/run.js
@@@ -655,6 +655,26 @@ await assert.rejects(failing.getToken(), /401: invalid_client/);
}
}
++// ── Moderation audit events (ADR-022, WS-D task 1): one shared payload, one event per producer ──
++// Events requires an event's prefix to be its source, so each service has its own type; the ones
++// without an older shape are common.moderation-action@1, and Network consumes every one of them.
++{
++ const moderation = contracts.catalog.filter(c => /\.moderation\.action$/.test(c.id));
++ for (const svc of ['tools', 'games', 'wiki', 'blog', 'news', 'reviews', 'deals', 'coupons', 'trade', 'codes']) {
++ const id = `${svc}.moderation.action`;
++ const c = moderation.find(x => x.id === id);
++ ok(c && c.owner === svc && c.adr === 'ADR-022', `${id} is owned by ${svc}`);
++ ok(contracts.schema(id).$ref === '../../common/moderation-action.v1.json', `${id} is a common.moderation-action@1`);
++ }
++ for (const c of moderation) ok(services.get('network').eventsConsumed.includes(c.id), `network consumes ${c.id} into the moderation audit log`);
++ const M = (v) => contracts.validate('common.moderation-action@1', v).valid;
++ const base = { action: 'post.hidden', target: { type: 'post', id: 'p1', owner_subject: null }, actor_subject: null };
++ ok(M(base) && M({ ...base, reason: 'spam', details: { previous: 'published' } }), 'action, target and actor_subject are enough; reason and details are optional');
++ for (const k of ['action', 'target', 'actor_subject']) ok(!M({ ...base, [k]: undefined }), `a moderation action needs ${k}`);
++ for (const action of ['Post.Hidden', 'post hidden', 'post.', '.hidden', 'x'.repeat(65)]) ok(!M({ ...base, action }), `"${action.slice(0, 12)}" is not a verb id`);
++ ok(!M({ ...base, text: 'the post' }) && !M({ ...base, target: { ...base.target, title: 'Hi' } }), 'it never carries the content');
++}
++
// ── Capability schemas (WS-C task 4): a ratchet over compatibility/capability-schema-gaps.json ──
execFileSync(process.execPath, [path.join(__dirname, 'capability-schemas.test.js')], { stdio: 'inherit' });