diff --git a/lexicons/app/bsky/embed/external.json b/lexicons/app/bsky/embed/external.json index cb06a2bc..7caf008c 100644 --- a/lexicons/app/bsky/embed/external.json +++ b/lexicons/app/bsky/embed/external.json @@ -79,12 +79,21 @@ "ref": "com.atproto.repo.strongRef" }, "description": "StrongRefs (uri+cid) of the Atmosphere records that backed this view." + }, + "associatedProfiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + }, + "description": "Profiles of the owners of the Atmosphere records that backed this view." } } }, "viewExternalSource": { "type": "object", "description": "The source of an external embed, such as a standard.site publication.", + "required": ["uri", "title"], "properties": { "uri": { "type": "string", diff --git a/lexicons/app/bsky/embed/getEmbedExternalView.json b/lexicons/app/bsky/embed/getEmbedExternalView.json index 2b52ddb1..421fd3da 100644 --- a/lexicons/app/bsky/embed/getEmbedExternalView.json +++ b/lexicons/app/bsky/embed/getEmbedExternalView.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "Resolve one or more AT-URIs into the data needed to render an enhanced external embed. Returns `associatedRefs` (strongRefs to embed into a post's external.associatedRefs) and the raw `associatedRecords`; `view` is included when at least one record was hydrated, but may be omitted otherwise. When `view` is present, `view.external.title` and `view.external.description` may be empty strings if the resolved records didn't supply them — clients should treat empty values as 'no enrichment for this field' rather than the content being genuinely titleless.", + "description": "Resolve one or more AT-URIs into the data needed to render an enhanced external embed. Returns `associatedRefs` (strongRefs to embed into a post's external.associatedRefs), the raw `associatedRecords`, and a hydrated `view`. The response is empty (`{}`) when no records were resolvable, or when validation determined the resolved records don't actually back the requested URL; clients should fall back to their own link-card rendering in that case and skip writing strongRefs to the post.", "parameters": { "type": "params", "required": ["url", "uris"], @@ -30,7 +30,7 @@ "view": { "type": "ref", "ref": "app.bsky.embed.external#view", - "description": "Hydrated view of the embed. Present whenever at least one record was hydrated. `view.external.title` and `view.external.description` may be empty strings when the resolved records didn't supply them; clients should treat that as 'no enrichment for this field.'" + "description": "Hydrated view of the embed. Present only when the resolved records back the requested URL and supply enough information to populate the required `viewExternal` fields. Omitted alongside the rest of the response when no records resolved or validation failed." }, "associatedRefs": { "type": "array", diff --git a/lexicons/chat/bsky/convo/defs.json b/lexicons/chat/bsky/convo/defs.json index f78a1fab..0dc2eb89 100644 --- a/lexicons/chat/bsky/convo/defs.json +++ b/lexicons/chat/bsky/convo/defs.json @@ -47,7 +47,7 @@ }, "embed": { "type": "union", - "refs": ["app.bsky.embed.record"] + "refs": ["app.bsky.embed.record", "chat.bsky.embed.joinLink"] } } }, @@ -69,7 +69,10 @@ }, "embed": { "type": "union", - "refs": ["app.bsky.embed.record#view"] + "refs": [ + "app.bsky.embed.record#view", + "chat.bsky.embed.joinLink#view" + ] }, "reactions": { "type": "array", @@ -341,43 +344,47 @@ "description": "[NOTE: This is under active development and should be considered unstable while this note is here].", "type": "object", "required": [ - "name", + "createdAt", "lockStatus", "memberCount", "memberLimit", - "createdAt" + "name" ], "properties": { - "name": { - "type": "string", - "description": "The display name of the group conversation.", - "maxGraphemes": 128, - "maxLength": 1280 - }, - "memberCount": { - "type": "integer", - "description": "The total number of members in the group conversation." - }, "createdAt": { "type": "string", "format": "datetime" }, - "joinRequestCount": { - "type": "integer", - "description": "The total number of pending join requests for the group conversation. Only present for the owner. Capped at 21." - }, "joinLink": { "type": "ref", "ref": "chat.bsky.group.defs#joinLinkView" }, - "memberLimit": { + "joinRequestCount": { "type": "integer", - "description": "The maximum number of members allowed in the group conversation." + "description": "The total number of pending join requests for the group conversation. Only present for the owner. Capped at 21." }, "lockStatus": { "description": "The lock status of the conversation.", "type": "ref", "ref": "#convoLockStatus" + }, + "memberCount": { + "type": "integer", + "description": "The total number of members in the group conversation." + }, + "memberLimit": { + "type": "integer", + "description": "The maximum number of members allowed in the group conversation." + }, + "name": { + "type": "string", + "description": "The display name of the group conversation.", + "maxGraphemes": 128, + "maxLength": 1280 + }, + "unreadJoinRequestCount": { + "type": "integer", + "description": "The number of unread join requests for the group conversation. Only present for the owner." } } }, @@ -797,7 +804,39 @@ } }, "logOutgoingJoinRequest": { - "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was made by the viewer.", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a join request was made by the requester. Only requester actor gets this.", + "type": "object", + "required": ["rev", "convoId"], + "properties": { + "rev": { "type": "string" }, + "convoId": { "type": "string" } + } + }, + "logWithdrawIncomingJoinRequest": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating a prospective member withdrew their join request. Only the owner gets this.", + "type": "object", + "required": ["rev", "convoId", "member"], + "properties": { + "rev": { "type": "string" }, + "convoId": { "type": "string" }, + "member": { + "description": "Prospective member who withdrew their join request.", + "type": "ref", + "ref": "chat.bsky.actor.defs#profileViewBasic" + } + } + }, + "logWithdrawOutgoingJoinRequest": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating the viewer withdrew their own join request. Only requester actor gets this.", + "type": "object", + "required": ["rev", "convoId"], + "properties": { + "rev": { "type": "string" }, + "convoId": { "type": "string" } + } + }, + "logReadJoinRequests": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Event indicating the group owner marked join requests as read. Only the owner gets this.", "type": "object", "required": ["rev", "convoId"], "properties": { diff --git a/lexicons/chat/bsky/convo/getLog.json b/lexicons/chat/bsky/convo/getLog.json index 56315ca8..52dde621 100644 --- a/lexicons/chat/bsky/convo/getLog.json +++ b/lexicons/chat/bsky/convo/getLog.json @@ -54,7 +54,10 @@ "chat.bsky.convo.defs#logIncomingJoinRequest", "chat.bsky.convo.defs#logApproveJoinRequest", "chat.bsky.convo.defs#logRejectJoinRequest", - "chat.bsky.convo.defs#logOutgoingJoinRequest" + "chat.bsky.convo.defs#logOutgoingJoinRequest", + "chat.bsky.convo.defs#logWithdrawIncomingJoinRequest", + "chat.bsky.convo.defs#logWithdrawOutgoingJoinRequest", + "chat.bsky.convo.defs#logReadJoinRequests" ] } } diff --git a/lexicons/chat/bsky/embed/joinLink.json b/lexicons/chat/bsky/embed/joinLink.json new file mode 100644 index 00000000..77eda4e6 --- /dev/null +++ b/lexicons/chat/bsky/embed/joinLink.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "chat.bsky.embed.joinLink", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. A join link embedded in a chat message.", + "defs": { + "main": { + "type": "object", + "required": ["code"], + "properties": { + "code": { + "type": "string", + "description": "The join link code." + } + } + }, + "view": { + "type": "object", + "required": ["joinLinkPreview"], + "properties": { + "joinLinkPreview": { + "type": "ref", + "ref": "chat.bsky.group.defs#joinLinkPreviewView" + } + } + } + } +} diff --git a/lexicons/chat/bsky/group/getJoinLinkPreviews.json b/lexicons/chat/bsky/group/getJoinLinkPreviews.json index 98f13090..91c8fb5f 100644 --- a/lexicons/chat/bsky/group/getJoinLinkPreviews.json +++ b/lexicons/chat/bsky/group/getJoinLinkPreviews.json @@ -4,7 +4,7 @@ "defs": { "main": { "type": "query", - "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Get public information about groups from join links. Invalid or disabled codes are silently omitted from results.", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Get public information about groups from join links. Invalid or disabled codes are silently omitted from results. Use the 'code' property on the views to correlate with the input codes, not array positions.", "parameters": { "type": "params", "required": ["codes"], diff --git a/lexicons/chat/bsky/group/updateJoinRequestsRead.json b/lexicons/chat/bsky/group/updateJoinRequestsRead.json new file mode 100644 index 00000000..58d42926 --- /dev/null +++ b/lexicons/chat/bsky/group/updateJoinRequestsRead.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "chat.bsky.group.updateJoinRequestsRead", + "defs": { + "main": { + "type": "procedure", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Marks all join requests as read for the group owner.", + "errors": [{ "name": "InvalidConvo" }, { "name": "InsufficientRole" }], + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["convoId"], + "properties": { + "convoId": { "type": "string" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [], + "properties": {} + } + } + } + } +} diff --git a/lexicons/chat/bsky/group/withdrawJoinRequest.json b/lexicons/chat/bsky/group/withdrawJoinRequest.json new file mode 100644 index 00000000..fc0c8bf1 --- /dev/null +++ b/lexicons/chat/bsky/group/withdrawJoinRequest.json @@ -0,0 +1,29 @@ +{ + "lexicon": 1, + "id": "chat.bsky.group.withdrawJoinRequest", + "defs": { + "main": { + "type": "procedure", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Withdraws a pending request to join a group. Action taken by the prospective member who originally requested to join.", + "errors": [{ "name": "InvalidJoinRequest" }], + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["convoId"], + "properties": { + "convoId": { "type": "string" } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [], + "properties": {} + } + } + } + } +} diff --git a/lexicons/chat/bsky/moderation/defs.json b/lexicons/chat/bsky/moderation/defs.json new file mode 100644 index 00000000..9937055a --- /dev/null +++ b/lexicons/chat/bsky/moderation/defs.json @@ -0,0 +1,70 @@ +{ + "lexicon": 1, + "id": "chat.bsky.moderation.defs", + "defs": { + "convoView": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members.", + "type": "object", + "required": ["id", "rev"], + "properties": { + "id": { "type": "string" }, + "rev": { "type": "string" }, + "kind": { + "description": "Union field that has data specific to different kinds of convos.", + "type": "union", + "refs": ["#directConvo", "#groupConvo"] + } + } + }, + "directConvo": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Data specific to a direct conversation, for moderation purposes.", + "type": "object", + "properties": {} + }, + "groupConvo": { + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation.", + "type": "object", + "required": [ + "createdAt", + "joinRequestCount", + "lockStatus", + "memberCount", + "memberLimit", + "name" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "datetime" + }, + "joinLink": { + "type": "ref", + "ref": "chat.bsky.group.defs#joinLinkView" + }, + "joinRequestCount": { + "type": "integer", + "description": "The total number of pending join requests for the group conversation. This information is only visible to the owner and to moderators. Capped at 21." + }, + "lockStatus": { + "description": "The lock status of the conversation.", + "type": "ref", + "ref": "chat.bsky.convo.defs#convoLockStatus" + }, + "memberCount": { + "type": "integer", + "description": "The total number of members in the group conversation." + }, + "memberLimit": { + "type": "integer", + "description": "The maximum number of members allowed in the group conversation." + }, + "name": { + "type": "string", + "description": "The display name of the group conversation.", + "maxGraphemes": 128, + "maxLength": 1280 + } + } + } + } +} diff --git a/lexicons/chat/bsky/moderation/getConvo.json b/lexicons/chat/bsky/moderation/getConvo.json new file mode 100644 index 00000000..993fe864 --- /dev/null +++ b/lexicons/chat/bsky/moderation/getConvo.json @@ -0,0 +1,31 @@ +{ + "lexicon": 1, + "id": "chat.bsky.moderation.getConvo", + "defs": { + "main": { + "type": "query", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Gets an existing conversation by its ID, for moderation purposes. Does not require the requester to be a member of the conversation.", + "errors": [{ "name": "InvalidConvo" }], + "parameters": { + "type": "params", + "required": ["convoId"], + "properties": { + "convoId": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["convo"], + "properties": { + "convo": { + "type": "ref", + "ref": "chat.bsky.moderation.defs#convoView" + } + } + } + } + } + } +} diff --git a/lexicons/chat/bsky/moderation/getConvoMembers.json b/lexicons/chat/bsky/moderation/getConvoMembers.json new file mode 100644 index 00000000..3378518d --- /dev/null +++ b/lexicons/chat/bsky/moderation/getConvoMembers.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "chat.bsky.moderation.getConvoMembers", + "defs": { + "main": { + "type": "query", + "description": "[NOTE: This is under active development and should be considered unstable while this note is here]. Returns a paginated list of members from a conversation, for moderation purposes. Does not require the requester to be a member of the conversation.", + "errors": [{ "name": "InvalidConvo" }], + "parameters": { + "type": "params", + "required": ["convoId"], + "properties": { + "convoId": { "type": "string" }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["members"], + "properties": { + "cursor": { "type": "string" }, + "members": { + "type": "array", + "items": { + "type": "ref", + "ref": "chat.bsky.actor.defs#profileViewBasic" + } + } + } + } + } + } + } +} diff --git a/lexicons/chat/bsky/moderation/subscribeModEvents.json b/lexicons/chat/bsky/moderation/subscribeModEvents.json index 6e718438..8e05a726 100644 --- a/lexicons/chat/bsky/moderation/subscribeModEvents.json +++ b/lexicons/chat/bsky/moderation/subscribeModEvents.json @@ -27,7 +27,8 @@ "#eventGroupChatJoinRequestRejected", "#eventChatAccepted", "#eventGroupChatMemberLeft", - "#eventGroupChatUpdated" + "#eventGroupChatUpdated", + "#eventRateLimitExceeded" ] } }, @@ -535,6 +536,24 @@ ] } } + }, + "eventRateLimitExceeded": { + "type": "object", + "description": "Fired when a user exceeds a rate limit.", + "required": ["actorDid", "createdAt", "endpoint", "rev"], + "properties": { + "actorDid": { + "description": "The DID of the user who hit the rate limit.", + "type": "string", + "format": "did" + }, + "createdAt": { "type": "string", "format": "datetime" }, + "endpoint": { + "description": "The NSID of the endpoint that was rate limited.", + "type": "string" + }, + "rev": { "type": "string" } + } } } } diff --git a/lexicons/com/atproto/label/defs.json b/lexicons/com/atproto/label/defs.json index 6f4c1ab9..26b6a3f9 100644 --- a/lexicons/com/atproto/label/defs.json +++ b/lexicons/com/atproto/label/defs.json @@ -140,16 +140,13 @@ "type": "string", "knownValues": [ "!hide", - "!no-promote", "!warn", "!no-unauthenticated", - "dmca-violation", - "doxxing", "porn", "sexual", "nudity", - "nsfl", - "gore" + "graphic-media", + "bot" ] } } diff --git a/lexicons/com/atproto/lexicon/resolveLexicon.json b/lexicons/com/atproto/lexicon/resolveLexicon.json new file mode 100644 index 00000000..b74b79c5 --- /dev/null +++ b/lexicons/com/atproto/lexicon/resolveLexicon.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "com.atproto.lexicon.resolveLexicon", + "defs": { + "main": { + "type": "query", + "description": "Resolves an atproto lexicon (NSID) to a schema.", + "parameters": { + "type": "params", + "properties": { + "nsid": { + "format": "nsid", + "type": "string", + "description": "The lexicon NSID to resolve." + } + }, + "required": ["nsid"] + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the lexicon schema record." + }, + "schema": { + "type": "ref", + "ref": "com.atproto.lexicon.schema", + "description": "The resolved lexicon schema record." + }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "The AT-URI of the lexicon schema record." + } + }, + "required": ["uri", "cid", "schema"] + } + }, + "errors": [ + { + "description": "No lexicon was resolved for the NSID.", + "name": "LexiconNotFound" + } + ] + } + } +} diff --git a/lexicons/com/atproto/server/deleteSession.json b/lexicons/com/atproto/server/deleteSession.json index 807a89dc..55c8bb28 100644 --- a/lexicons/com/atproto/server/deleteSession.json +++ b/lexicons/com/atproto/server/deleteSession.json @@ -4,7 +4,8 @@ "defs": { "main": { "type": "procedure", - "description": "Delete the current session. Requires auth." + "description": "Delete the current session. Requires auth using the 'refreshJwt' (not the 'accessJwt').", + "errors": [{ "name": "InvalidToken" }, { "name": "ExpiredToken" }] } } } diff --git a/lexicons/com/atproto/server/getServiceAuth.json b/lexicons/com/atproto/server/getServiceAuth.json index 37c150d7..e0c69476 100644 --- a/lexicons/com/atproto/server/getServiceAuth.json +++ b/lexicons/com/atproto/server/getServiceAuth.json @@ -11,8 +11,8 @@ "properties": { "aud": { "type": "string", - "format": "did", - "description": "The DID of the service that the token will be used to authenticate with" + "maxLength": 2048, + "description": "The DID or `did#serviceId` reference of the service that the token will be used to authenticate with." }, "exp": { "type": "integer", diff --git a/lexicons/com/atproto/server/getSession.json b/lexicons/com/atproto/server/getSession.json index 86efa746..59863613 100644 --- a/lexicons/com/atproto/server/getSession.json +++ b/lexicons/com/atproto/server/getSession.json @@ -13,10 +13,10 @@ "properties": { "handle": { "type": "string", "format": "handle" }, "did": { "type": "string", "format": "did" }, + "didDoc": { "type": "unknown" }, "email": { "type": "string" }, "emailConfirmed": { "type": "boolean" }, "emailAuthFactor": { "type": "boolean" }, - "didDoc": { "type": "unknown" }, "active": { "type": "boolean" }, "status": { "type": "string", diff --git a/lexicons/com/atproto/server/refreshSession.json b/lexicons/com/atproto/server/refreshSession.json index 71123a4f..e22acb89 100644 --- a/lexicons/com/atproto/server/refreshSession.json +++ b/lexicons/com/atproto/server/refreshSession.json @@ -16,6 +16,9 @@ "handle": { "type": "string", "format": "handle" }, "did": { "type": "string", "format": "did" }, "didDoc": { "type": "unknown" }, + "email": { "type": "string" }, + "emailConfirmed": { "type": "boolean" }, + "emailAuthFactor": { "type": "boolean" }, "active": { "type": "boolean" }, "status": { "type": "string", @@ -25,7 +28,11 @@ } } }, - "errors": [{ "name": "AccountTakedown" }] + "errors": [ + { "name": "AccountTakedown" }, + { "name": "InvalidToken" }, + { "name": "ExpiredToken" } + ] } } } diff --git a/lexicons/com/germnetwork/declaration.json b/lexicons/com/germnetwork/declaration.json new file mode 100644 index 00000000..bce08581 --- /dev/null +++ b/lexicons/com/germnetwork/declaration.json @@ -0,0 +1,64 @@ +{ + "lexicon": 1, + "id": "com.germnetwork.declaration", + "defs": { + "main": { + "type": "record", + "description": "A declaration of a Germ Network account", + "key": "literal:self", + "record": { + "type": "object", + "required": ["version", "currentKey"], + "properties": { + "version": { + "type": "string", + "description": "Semver version number, without pre-release or build information, for the format of opaque content", + "minLength": 5, + "maxLength": 14 + }, + "currentKey": { + "type": "bytes", + "description": "Opaque value, an ed25519 public key prefixed with a byte enum" + }, + "messageMe": { + "type": "ref", + "description": "Controls who can message this account", + "ref": "#messageMe" + }, + "keyPackage": { + "type": "bytes", + "description": "Opaque value, contains MLS KeyPackage(s), and other signature data, and is signed by the currentKey" + }, + "continuityProofs": { + "type": "array", + "description": "Array of opaque values to allow for key rolling", + "items": { + "type": "bytes" + }, + "maxLength": 1000 + } + } + } + }, + "messageMe": { + "type": "object", + "required": ["showButtonTo", "messageMeUrl"], + "properties": { + "messageMeUrl": { + "type": "string", + "description": "A URL to present to an account that does not have its own com.germnetwork.declaration record, must have an empty fragment component, where the app should fill in the fragment component with the DIDs of the two accounts who wish to message each other", + "format": "uri", + "minLength": 1, + "maxLength": 2047 + }, + "showButtonTo": { + "type": "string", + "knownValues": ["none", "usersIFollow", "everyone"], + "description": "The policy of who can message the account, this value is included in the keyPackage, but is duplicated here to allow applications to decide if they should show a 'Message on Germ' button to the viewer.", + "minLength": 1, + "maxLength": 100 + } + } + } + } +} diff --git a/lexicons/site/standard/document.json b/lexicons/site/standard/document.json index 07f13021..c2231e9d 100644 --- a/lexicons/site/standard/document.json +++ b/lexicons/site/standard/document.json @@ -1,10 +1,5 @@ { "defs": { - "placeholder": { - "description": "Placeholder type used to seed open unions until concrete content/link lexicons are defined. Should not be used in real records.", - "properties": {}, - "type": "object" - }, "contributor": { "properties": { "did": { @@ -22,9 +17,7 @@ "type": "string" } }, - "required": [ - "did" - ], + "required": ["did"], "type": "object" }, "main": { @@ -40,9 +33,7 @@ "content": { "closed": false, "description": "Open union used to define the record's content. Each entry must specify a $type and may be extended with other lexicons to support additional content formats.", - "refs": [ - "#placeholder" - ], + "refs": [], "type": "union" }, "contributors": { @@ -53,9 +44,7 @@ "type": "array" }, "coverImage": { - "accept": [ - "image/*" - ], + "accept": ["image/*"], "description": "Image to used for thumbnail or cover image. Less than 1MB is size.", "maxSize": 1000000, "type": "blob" @@ -68,16 +57,12 @@ }, "labels": { "description": "Self-label values for this post. Effectively content warnings.", - "refs": [ - "com.atproto.label.defs#selfLabels" - ], + "refs": ["com.atproto.label.defs#selfLabels"], "type": "union" }, "links": { "description": "Array of values describing relationships between this document and external resources", - "refs": [ - "#placeholder" - ], + "refs": [], "type": "union" }, "path": { @@ -119,11 +104,7 @@ "type": "string" } }, - "required": [ - "site", - "title", - "publishedAt" - ], + "required": ["site", "title", "publishedAt"], "type": "object" }, "type": "record" diff --git a/lexicons/site/standard/graph/recommend.json b/lexicons/site/standard/graph/recommend.json index 27625ddf..51477b6c 100644 --- a/lexicons/site/standard/graph/recommend.json +++ b/lexicons/site/standard/graph/recommend.json @@ -15,10 +15,7 @@ "type": "string" } }, - "required": [ - "document", - "createdAt" - ], + "required": ["document", "createdAt"], "type": "object" }, "type": "record" diff --git a/lexicons/site/standard/graph/subscription.json b/lexicons/site/standard/graph/subscription.json index f0da5f7a..e590b2d6 100644 --- a/lexicons/site/standard/graph/subscription.json +++ b/lexicons/site/standard/graph/subscription.json @@ -15,9 +15,7 @@ "type": "string" } }, - "required": [ - "publication" - ], + "required": ["publication"], "type": "object" }, "type": "record" diff --git a/lexicons/site/standard/publication.json b/lexicons/site/standard/publication.json index a93f818d..249c9f1f 100644 --- a/lexicons/site/standard/publication.json +++ b/lexicons/site/standard/publication.json @@ -17,18 +17,14 @@ "type": "string" }, "icon": { - "accept": [ - "image/*" - ], + "accept": ["image/*"], "description": "Square image to identify the publication. Should be at least 256x256.", "maxSize": 1000000, "type": "blob" }, "labels": { "description": "Self-label values for this publication. Effectively content warnings.", - "refs": [ - "com.atproto.label.defs#selfLabels" - ], + "refs": ["com.atproto.label.defs#selfLabels"], "type": "union" }, "name": { @@ -48,10 +44,7 @@ "type": "string" } }, - "required": [ - "url", - "name" - ], + "required": ["url", "name"], "type": "object" }, "type": "record" diff --git a/lexicons/site/standard/theme/basic.json b/lexicons/site/standard/theme/basic.json index 86969ff6..37b3f002 100644 --- a/lexicons/site/standard/theme/basic.json +++ b/lexicons/site/standard/theme/basic.json @@ -7,39 +7,26 @@ "properties": { "accent": { "description": "Color used for links and button backgrounds.", - "refs": [ - "site.standard.theme.color#rgb" - ], + "refs": ["site.standard.theme.color#rgb"], "type": "union" }, "accentForeground": { "description": "Color used for button text.", - "refs": [ - "site.standard.theme.color#rgb" - ], + "refs": ["site.standard.theme.color#rgb"], "type": "union" }, "background": { "description": "Color used for content background.", - "refs": [ - "site.standard.theme.color#rgb" - ], + "refs": ["site.standard.theme.color#rgb"], "type": "union" }, "foreground": { "description": "Color used for content text.", - "refs": [ - "site.standard.theme.color#rgb" - ], + "refs": ["site.standard.theme.color#rgb"], "type": "union" } }, - "required": [ - "background", - "foreground", - "accent", - "accentForeground" - ], + "required": ["background", "foreground", "accent", "accentForeground"], "type": "object" }, "type": "record" diff --git a/lexicons/site/standard/theme/color.json b/lexicons/site/standard/theme/color.json index 5b6282da..a4047de8 100644 --- a/lexicons/site/standard/theme/color.json +++ b/lexicons/site/standard/theme/color.json @@ -18,11 +18,7 @@ "type": "integer" } }, - "required": [ - "r", - "g", - "b" - ], + "required": ["r", "g", "b"], "type": "object" }, "rgba": { @@ -48,12 +44,7 @@ "type": "integer" } }, - "required": [ - "r", - "g", - "b", - "a" - ], + "required": ["r", "g", "b", "a"], "type": "object" } }, diff --git a/lexicons/tools/ozone/moderation/defs.json b/lexicons/tools/ozone/moderation/defs.json index 0ed8f27d..cff42b1d 100644 --- a/lexicons/tools/ozone/moderation/defs.json +++ b/lexicons/tools/ozone/moderation/defs.json @@ -49,7 +49,8 @@ "refs": [ "com.atproto.admin.defs#repoRef", "com.atproto.repo.strongRef", - "chat.bsky.convo.defs#messageRef" + "chat.bsky.convo.defs#messageRef", + "chat.bsky.convo.defs#convoRef" ] }, "subjectBlobCids": { "type": "array", "items": { "type": "string" } }, @@ -108,7 +109,8 @@ "#repoView", "#repoViewNotFound", "#recordView", - "#recordViewNotFound" + "#recordViewNotFound", + "#convoView" ] }, "subjectBlobs": { @@ -130,7 +132,8 @@ "refs": [ "com.atproto.admin.defs#repoRef", "com.atproto.repo.strongRef", - "chat.bsky.convo.defs#messageRef" + "chat.bsky.convo.defs#messageRef", + "chat.bsky.convo.defs#convoRef" ] }, "hosting": { @@ -739,6 +742,10 @@ "comment": { "type": "string", "description": "Additional comment about added/removed tags." + }, + "durationInHours": { + "type": "integer", + "description": "Indicates how long the tags being added should remain before automatically being removed. Only applies to tags being added." } } }, @@ -957,6 +964,14 @@ "uri": { "type": "string", "format": "at-uri" } } }, + "convoView": { + "type": "object", + "required": ["did", "convoId"], + "properties": { + "did": { "type": "string", "format": "did" }, + "convoId": { "type": "string" } + } + }, "moderation": { "type": "object", "properties": { diff --git a/lexicons/tools/ozone/moderation/emitEvent.json b/lexicons/tools/ozone/moderation/emitEvent.json index 256ef164..968946ac 100644 --- a/lexicons/tools/ozone/moderation/emitEvent.json +++ b/lexicons/tools/ozone/moderation/emitEvent.json @@ -66,6 +66,11 @@ "externalId": { "type": "string", "description": "An optional external ID for the event, used to deduplicate events from external systems. Fails when an event of same type with the same external ID exists for the same subject." + }, + "reportAction": { + "type": "ref", + "ref": "#reportAction", + "description": "Optional report-level targeting. If provided, this event will be linked to specific reports and reporters may be notified." } } } @@ -86,6 +91,34 @@ "description": "An event with the same external ID already exists for the subject." } ] + }, + "reportAction": { + "type": "object", + "description": "Target specific reports when emitting a moderation event", + "properties": { + "ids": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Target specific report IDs" + }, + "types": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Target reports matching these report types on the subject (fully qualified NSIDs)" + }, + "all": { + "type": "boolean", + "description": "Target ALL reports on the subject" + }, + "note": { + "type": "string", + "description": "Note to send to reporter(s) when actioning their report" + } + } } } } diff --git a/lexicons/tools/ozone/moderation/queryEvents.json b/lexicons/tools/ozone/moderation/queryEvents.json index 4c011d6c..0f6227f3 100644 --- a/lexicons/tools/ozone/moderation/queryEvents.json +++ b/lexicons/tools/ozone/moderation/queryEvents.json @@ -50,8 +50,8 @@ }, "subjectType": { "type": "string", - "description": "If specified, only events where the subject is of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.", - "knownValues": ["account", "record"] + "description": "If specified, only events where the subject is of the given type (account, record, or conversation) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.", + "knownValues": ["account", "record", "conversation"] }, "includeAllUserRecords": { "type": "boolean", diff --git a/lexicons/tools/ozone/moderation/queryStatuses.json b/lexicons/tools/ozone/moderation/queryStatuses.json index 738d83ee..57157df0 100644 --- a/lexicons/tools/ozone/moderation/queryStatuses.json +++ b/lexicons/tools/ozone/moderation/queryStatuses.json @@ -168,8 +168,8 @@ }, "subjectType": { "type": "string", - "description": "If specified, subjects of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.", - "knownValues": ["account", "record"] + "description": "If specified, subjects of the given type (account, record, or conversation) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored.", + "knownValues": ["account", "record", "conversation"] }, "minAccountSuspendCount": { "type": "integer", diff --git a/lexicons/tools/ozone/queue/assignModerator.json b/lexicons/tools/ozone/queue/assignModerator.json new file mode 100644 index 00000000..e38c8afc --- /dev/null +++ b/lexicons/tools/ozone/queue/assignModerator.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.assignModerator", + "defs": { + "main": { + "type": "procedure", + "description": "Assign a user to a queue.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queueId", "did"], + "properties": { + "queueId": { + "type": "integer", + "description": "The ID of the queue to assign the user to." + }, + "did": { + "type": "string", + "description": "DID to be assigned." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "tools.ozone.queue.defs#assignmentView" + } + }, + "errors": [ + { + "name": "InvalidAssignment", + "description": "The specified queue does not exist or is not enabled." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/queue/createQueue.json b/lexicons/tools/ozone/queue/createQueue.json new file mode 100644 index 00000000..a7a42a23 --- /dev/null +++ b/lexicons/tools/ozone/queue/createQueue.json @@ -0,0 +1,69 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.createQueue", + "defs": { + "main": { + "type": "procedure", + "description": "Create a new moderation queue. Will fail if the queue configuration conflicts with an existing queue.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["name", "subjectTypes", "reportTypes"], + "properties": { + "name": { + "type": "string", + "description": "Display name for the queue (must be unique)" + }, + "subjectTypes": { + "type": "array", + "minLength": 1, + "items": { + "type": "string", + "knownValues": ["account", "record", "message"] + }, + "description": "Subject types this queue accepts" + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "Collection name for record subjects. Required if subjectTypes includes 'record'." + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "maxLength": 25, + "description": "Report reason types (fully qualified NSIDs)" + }, + "description": { + "type": "string", + "description": "Optional description of the queue" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queue"], + "properties": { + "queue": { + "type": "ref", + "ref": "tools.ozone.queue.defs#queueView" + } + } + } + }, + "errors": [ + { + "name": "ConflictingQueue", + "description": "The queue configuration conflicts with an existing queue" + } + ] + } + } +} diff --git a/lexicons/tools/ozone/queue/defs.json b/lexicons/tools/ozone/queue/defs.json new file mode 100644 index 00000000..81890d80 --- /dev/null +++ b/lexicons/tools/ozone/queue/defs.json @@ -0,0 +1,148 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.defs", + "defs": { + "queueView": { + "type": "object", + "required": [ + "id", + "name", + "subjectTypes", + "reportTypes", + "createdBy", + "createdAt", + "updatedAt", + "enabled", + "stats" + ], + "properties": { + "id": { + "type": "integer", + "description": "Queue ID" + }, + "name": { + "type": "string", + "description": "Display name of the queue" + }, + "subjectTypes": { + "type": "array", + "minLength": 1, + "items": { + "type": "string", + "knownValues": ["account", "record", "message"] + }, + "description": "Subject types this queue accepts." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "Collection name for record subjects (e.g., 'app.bsky.feed.post')" + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + }, + "minLength": 1, + "description": "Report reason types this queue accepts (fully qualified NSIDs)" + }, + "description": { + "type": "string", + "description": "Optional description of the queue" + }, + "createdBy": { + "type": "string", + "format": "did", + "description": "DID of moderator who created this queue" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "updatedAt": { + "type": "string", + "format": "datetime" + }, + "enabled": { + "type": "boolean", + "description": "Whether this queue is currently active" + }, + "deletedAt": { + "type": "string", + "format": "datetime", + "description": "When the queue was deleted, if applicable" + }, + "stats": { + "type": "ref", + "ref": "#queueStats", + "description": "Statistics about this queue" + } + } + }, + "queueStats": { + "type": "object", + "required": [], + "properties": { + "pendingCount": { + "type": "integer", + "description": "Number of reports in 'open' status" + }, + "actionedCount": { + "type": "integer", + "description": "Number of reports in 'closed' status" + }, + "escalatedCount": { + "type": "integer", + "description": "Number of reports in 'escalated' status" + }, + "inboundCount": { + "type": "integer", + "description": "Reports received in this queue in the last 24 hours." + }, + "actionRate": { + "type": "integer", + "description": "Percentage of reports actioned (actionedCount / inboundCount * 100), rounded to nearest integer. Absent when inboundCount is 0." + }, + "avgHandlingTimeSec": { + "type": "integer", + "description": "Average time in seconds from report creation to close, for reports closed in this period." + }, + "lastUpdated": { + "type": "string", + "format": "datetime", + "description": "When these statistics were last computed" + } + } + }, + "assignmentView": { + "type": "object", + "required": ["id", "did", "queue", "startAt"], + "properties": { + "id": { + "type": "integer" + }, + "did": { + "type": "string", + "format": "did" + }, + "moderator": { + "type": "ref", + "ref": "tools.ozone.team.defs#member", + "description": "The moderator assigned to this queue" + }, + "queue": { + "type": "ref", + "ref": "#queueView" + }, + "startAt": { + "type": "string", + "format": "datetime" + }, + "endAt": { + "type": "string", + "format": "datetime" + } + } + } + } +} diff --git a/lexicons/tools/ozone/queue/deleteQueue.json b/lexicons/tools/ozone/queue/deleteQueue.json new file mode 100644 index 00000000..29a37352 --- /dev/null +++ b/lexicons/tools/ozone/queue/deleteQueue.json @@ -0,0 +1,43 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.deleteQueue", + "defs": { + "main": { + "type": "procedure", + "description": "Delete a moderation queue. Optionally migrate reports to another queue.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queueId"], + "properties": { + "queueId": { + "type": "integer", + "description": "ID of the queue to delete" + }, + "migrateToQueueId": { + "type": "integer", + "description": "Optional: migrate all reports to this queue. If not specified, reports will be set to unassigned (-1)." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["deleted"], + "properties": { + "deleted": { + "type": "boolean" + }, + "reportsMigrated": { + "type": "integer", + "description": "Number of reports that were migrated (if migration occurred)" + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/queue/getAssignments.json b/lexicons/tools/ozone/queue/getAssignments.json new file mode 100644 index 00000000..2eb5c65b --- /dev/null +++ b/lexicons/tools/ozone/queue/getAssignments.json @@ -0,0 +1,63 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.getAssignments", + "defs": { + "main": { + "type": "query", + "description": "Get moderator assignments, optionally filtered by active status, queue, or moderator.", + "parameters": { + "type": "params", + "properties": { + "onlyActive": { + "type": "boolean", + "default": true, + "description": "When true, only returns active assignments." + }, + "queueIds": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "If specified, returns assignments for these queues only." + }, + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "description": "If specified, returns assignments for these moderators only." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["assignments"], + "properties": { + "cursor": { + "type": "string" + }, + "assignments": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.queue.defs#assignmentView" + } + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/queue/listQueues.json b/lexicons/tools/ozone/queue/listQueues.json new file mode 100644 index 00000000..e9f9caf4 --- /dev/null +++ b/lexicons/tools/ozone/queue/listQueues.json @@ -0,0 +1,61 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.listQueues", + "defs": { + "main": { + "type": "query", + "description": "List all configured moderation queues with statistics.", + "parameters": { + "type": "params", + "properties": { + "enabled": { + "type": "boolean", + "description": "Filter by enabled status. If not specified, returns all queues." + }, + "subjectType": { + "type": "string", + "description": "Filter queues that handle this subject type ('account' or 'record')." + }, + "collection": { + "type": "string", + "description": "Filter queues by collection name (e.g. 'app.bsky.feed.post')." + }, + "reportTypes": { + "type": "array", + "items": { "type": "string" }, + "maxLength": 10, + "description": "Filter queues that handle any of these report reason types." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queues"], + "properties": { + "cursor": { + "type": "string" + }, + "queues": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.queue.defs#queueView" + } + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/queue/routeReports.json b/lexicons/tools/ozone/queue/routeReports.json new file mode 100644 index 00000000..d69ab68f --- /dev/null +++ b/lexicons/tools/ozone/queue/routeReports.json @@ -0,0 +1,50 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.routeReports", + "defs": { + "main": { + "type": "procedure", + "description": "Route reports within an ID range to matching queues based.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["startReportId", "endReportId"], + "properties": { + "startReportId": { + "type": "integer", + "description": "Start of report ID range (inclusive)." + }, + "endReportId": { + "type": "integer", + "description": "End of report ID range (inclusive). Difference between start and end must be less than 5,000." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["assigned", "unmatched"], + "properties": { + "assigned": { + "type": "integer", + "description": "The number of reports assigned to a queue." + }, + "unmatched": { + "type": "integer", + "description": "The number of reports with no matching queue." + } + } + } + }, + "errors": [ + { + "name": "OutOfRange", + "description": "The request is invalid, such as missing required fields or invalid field values." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/queue/unassignModerator.json b/lexicons/tools/ozone/queue/unassignModerator.json new file mode 100644 index 00000000..633ac817 --- /dev/null +++ b/lexicons/tools/ozone/queue/unassignModerator.json @@ -0,0 +1,34 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.unassignModerator", + "defs": { + "main": { + "type": "procedure", + "description": "Remove a user's assignment from a queue.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queueId", "did"], + "properties": { + "queueId": { + "type": "integer", + "description": "The ID of the queue to unassign the user from." + }, + "did": { + "type": "string", + "format": "did", + "description": "DID to be unassigned." + } + } + } + }, + "errors": [ + { + "name": "InvalidAssignment", + "description": "No active assignment exists for the given queue and user." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/queue/updateQueue.json b/lexicons/tools/ozone/queue/updateQueue.json new file mode 100644 index 00000000..a813f427 --- /dev/null +++ b/lexicons/tools/ozone/queue/updateQueue.json @@ -0,0 +1,48 @@ +{ + "lexicon": 1, + "id": "tools.ozone.queue.updateQueue", + "defs": { + "main": { + "type": "procedure", + "description": "Update queue properties. Currently only supports updating the name and enabled status to prevent configuration conflicts.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queueId"], + "properties": { + "queueId": { + "type": "integer", + "description": "ID of the queue to update" + }, + "name": { + "type": "string", + "description": "New display name for the queue" + }, + "enabled": { + "type": "boolean", + "description": "Enable or disable the queue" + }, + "description": { + "type": "string", + "description": "Optional description of the queue" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["queue"], + "properties": { + "queue": { + "type": "ref", + "ref": "tools.ozone.queue.defs#queueView" + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/assignModerator.json b/lexicons/tools/ozone/report/assignModerator.json new file mode 100644 index 00000000..f7ec7547 --- /dev/null +++ b/lexicons/tools/ozone/report/assignModerator.json @@ -0,0 +1,53 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.assignModerator", + "defs": { + "main": { + "type": "procedure", + "description": "Assign a report to a user. Defaults to the caller. Admins may assign to any moderator.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["reportId"], + "properties": { + "reportId": { + "type": "integer", + "description": "The ID of the report to assign." + }, + "queueId": { + "type": "integer", + "description": "Optional queue ID to associate the assignment with. If not provided and the report has been assigned on a queue before, it will stay on that queue." + }, + "did": { + "type": "string", + "format": "did", + "description": "DID to be assigned. Defaults to the caller's DID. Admins may assign to any moderator." + }, + "isPermanent": { + "type": "boolean", + "description": "When true, the assignment has no expiry (endAt is null). Throws AlreadyAssigned if another user already has a permanent assignment on this report." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "tools.ozone.report.defs#assignmentView" + } + }, + "errors": [ + { + "name": "AlreadyAssigned", + "description": "The report is already assigned to another user." + }, + { + "name": "InvalidAssignment", + "description": "The report ID or queue ID is invalid." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/report/createActivity.json b/lexicons/tools/ozone/report/createActivity.json new file mode 100644 index 00000000..254df962 --- /dev/null +++ b/lexicons/tools/ozone/report/createActivity.json @@ -0,0 +1,75 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.createActivity", + "defs": { + "main": { + "type": "procedure", + "description": "Register an activity on a report. For state-change activity types, validates the transition and updates report.status atomically.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["reportId", "activity"], + "properties": { + "reportId": { + "type": "integer", + "description": "ID of the report to record activity on" + }, + "activity": { + "type": "union", + "refs": [ + "tools.ozone.report.defs#queueActivity", + "tools.ozone.report.defs#assignmentActivity", + "tools.ozone.report.defs#escalationActivity", + "tools.ozone.report.defs#closeActivity", + "tools.ozone.report.defs#reopenActivity", + "tools.ozone.report.defs#noteActivity" + ], + "description": "The type of activity to record." + }, + "internalNote": { + "type": "string", + "description": "Optional moderator-only note. Not visible to reporters." + }, + "publicNote": { + "type": "string", + "description": "Optional public-facing note, potentially visible to the reporter." + }, + "isAutomated": { + "type": "boolean", + "description": "Set true when this activity is triggered by an automated process. Defaults to false.", + "default": false + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["activity"], + "properties": { + "activity": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportActivityView" + } + } + } + }, + "errors": [ + { + "name": "ReportNotFound", + "description": "No report exists with the given reportId" + }, + { + "name": "InvalidStateTransition", + "description": "The requested state transition is not permitted from the report's current status" + }, + { + "name": "AlreadyInTargetState", + "description": "The report is already in the status implied by this activity type" + } + ] + } + } +} diff --git a/lexicons/tools/ozone/report/defs.json b/lexicons/tools/ozone/report/defs.json index 5eca2699..e5f6efd1 100644 --- a/lexicons/tools/ozone/report/defs.json +++ b/lexicons/tools/ozone/report/defs.json @@ -218,6 +218,370 @@ "reasonSelfHarmOther": { "type": "token", "description": "Other dangerous content" + }, + "reportAssignment": { + "type": "object", + "description": "Information about the moderator currently assigned to a report.", + "required": ["did", "assignedAt"], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "DID of the assigned moderator" + }, + "moderator": { + "type": "ref", + "ref": "tools.ozone.team.defs#member", + "description": "Full member record of the assigned moderator" + }, + "assignedAt": { + "type": "string", + "format": "datetime", + "description": "When the report was assigned" + } + } + }, + "reportView": { + "type": "object", + "required": [ + "id", + "eventId", + "status", + "subject", + "reportType", + "reportedBy", + "reporter", + "createdAt" + ], + "properties": { + "id": { + "type": "integer", + "description": "Report ID" + }, + "eventId": { + "type": "integer", + "description": "ID of the moderation event that created this report" + }, + "status": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "Current status of the report" + }, + "subject": { + "type": "ref", + "ref": "tools.ozone.moderation.defs#subjectView", + "description": "The subject that was reported with full details" + }, + "reportType": { + "type": "ref", + "ref": "com.atproto.moderation.defs#reasonType", + "description": "Type of report" + }, + "reportedBy": { + "type": "string", + "format": "did", + "description": "DID of the user who made the report" + }, + "reporter": { + "type": "ref", + "ref": "tools.ozone.moderation.defs#subjectView", + "description": "Full subject view of the reporter account" + }, + "comment": { + "type": "string", + "description": "Comment provided by the reporter" + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "When the report was created" + }, + "updatedAt": { + "type": "string", + "format": "datetime", + "description": "When the report was last updated" + }, + "queuedAt": { + "type": "string", + "format": "datetime", + "description": "When the report was assigned to its current queue" + }, + "actionEventIds": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Array of moderation event IDs representing actions taken on this report (sorted DESC, most recent first)" + }, + "actions": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.moderation.defs#modEventView" + }, + "description": "Optional: expanded action events" + }, + "actionNote": { + "type": "string", + "description": "Note sent to reporter when report was actioned" + }, + "subjectStatus": { + "type": "ref", + "ref": "tools.ozone.moderation.defs#subjectStatusView", + "description": "Current status of the reported subject" + }, + "relatedReportCount": { + "type": "integer", + "description": "Number of other pending reports on the same subject" + }, + "assignment": { + "type": "ref", + "ref": "#reportAssignment", + "description": "Information about moderator currently assigned to this report (if any)" + }, + "queue": { + "type": "ref", + "ref": "tools.ozone.queue.defs#queueView", + "description": "The queue this report is assigned to (if any)" + }, + "isMuted": { + "type": "boolean", + "description": "Whether this report is muted. A report is muted if the reporter was muted or the subject was muted at the time the report was created." + } + } + }, + "queueActivity": { + "type": "object", + "description": "Activity recording a report being routed to a queue.", + "properties": { + "previousStatus": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "The report's status before this activity. Populated automatically from the report row; not required in input." + } + } + }, + "assignmentActivity": { + "type": "object", + "description": "Activity recording a moderator being assigned to a report.", + "properties": { + "previousStatus": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "The report's status before this activity. Populated automatically from the report row; not required in input." + } + } + }, + "escalationActivity": { + "type": "object", + "description": "Activity recording a report being escalated.", + "properties": { + "previousStatus": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "The report's status before this activity. Populated automatically from the report row; not required in input." + } + } + }, + "closeActivity": { + "type": "object", + "description": "Activity recording a report being closed.", + "properties": { + "previousStatus": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "The report's status before this activity. Populated automatically from the report row; not required in input." + } + } + }, + "reopenActivity": { + "type": "object", + "description": "Activity recording a closed report being reopened. Only valid when the report is in 'closed' status.", + "properties": { + "previousStatus": { + "type": "string", + "knownValues": ["open", "closed", "escalated", "queued", "assigned"], + "description": "The report's status before this activity. Populated automatically from the report row; not required in input." + } + } + }, + "noteActivity": { + "type": "object", + "description": "Activity recording a note on a report. Use internalNote for moderator-only notes or publicNote for reporter-visible notes (or both).", + "properties": {} + }, + "reportActivityView": { + "type": "object", + "description": "A single activity entry on a report.", + "required": [ + "id", + "reportId", + "activity", + "isAutomated", + "createdBy", + "createdAt" + ], + "properties": { + "id": { + "type": "integer", + "description": "Activity ID" + }, + "reportId": { + "type": "integer", + "description": "ID of the report this activity belongs to" + }, + "activity": { + "type": "union", + "refs": [ + "#queueActivity", + "#assignmentActivity", + "#escalationActivity", + "#closeActivity", + "#reopenActivity", + "#noteActivity" + ], + "description": "The typed activity object describing what occurred." + }, + "internalNote": { + "type": "string", + "description": "Optional moderator-only note. Not visible to reporters." + }, + "publicNote": { + "type": "string", + "description": "Optional public note, potentially visible to the reporter." + }, + "meta": { + "type": "unknown", + "description": "Extensible JSON payload for loose activity-specific metadata (e.g. assignmentId)." + }, + "isAutomated": { + "type": "boolean", + "description": "True if this activity was created by an automated process (e.g. queue router) rather than a direct human action." + }, + "createdBy": { + "type": "string", + "format": "did", + "description": "DID of the actor who created this activity, or the service DID for automated activities." + }, + "moderator": { + "type": "ref", + "ref": "tools.ozone.team.defs#member", + "description": "Full member record of the moderator who created this activity" + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "When this activity was created" + } + } + }, + "liveStats": { + "description": "Live statistics for reports for the current calendar day, filterable by queue, moderator, or report type.", + "type": "object", + "properties": { + "pendingCount": { + "type": "integer", + "description": "Number of reports currently not closed." + }, + "actionedCount": { + "type": "integer", + "description": "Number of reports closed today." + }, + "escalatedCount": { + "type": "integer", + "description": "Number of reports escalated today." + }, + "inboundCount": { + "type": "integer", + "description": "Reports received today." + }, + "actionRate": { + "type": "integer", + "description": "Percentage of reports actioned (actionedCount / inboundCount * 100), rounded to nearest integer." + }, + "avgHandlingTimeSec": { + "type": "integer", + "description": "Average time in seconds from report creation (or moderator assignment) to close." + }, + "lastUpdated": { + "type": "string", + "format": "datetime", + "description": "When these statistics were last computed." + } + } + }, + "historicalStats": { + "description": "A single daily snapshot of report statistics for a calendar date.", + "type": "object", + "required": ["date"], + "properties": { + "date": { + "type": "string", + "description": "The calendar date this snapshot covers (YYYY-MM-DD)." + }, + "computedAt": { + "type": "string", + "format": "datetime", + "description": "When this snapshot was last computed." + }, + "pendingCount": { + "type": "integer", + "description": "Number of reports not closed at time of computation." + }, + "actionedCount": { + "type": "integer", + "description": "Number of reports closed during this day." + }, + "escalatedCount": { + "type": "integer", + "description": "Number of reports escalated during this day." + }, + "inboundCount": { + "type": "integer", + "description": "Reports received during this day." + }, + "actionRate": { + "type": "integer", + "description": "Percentage of reports actioned (actionedCount / inboundCount * 100), rounded to nearest integer." + }, + "avgHandlingTimeSec": { + "type": "integer", + "description": "Average time in seconds from report creation (or moderator assignment) to close." + } + } + }, + "assignmentView": { + "type": "object", + "required": ["id", "did", "reportId", "startAt"], + "properties": { + "id": { + "type": "integer" + }, + "did": { + "type": "string", + "format": "did" + }, + "moderator": { + "type": "ref", + "ref": "tools.ozone.team.defs#member", + "description": "The moderator assigned to this report" + }, + "queue": { + "type": "ref", + "ref": "tools.ozone.queue.defs#queueView" + }, + "reportId": { + "type": "integer" + }, + "startAt": { + "type": "string", + "format": "datetime" + }, + "endAt": { + "type": "string", + "format": "datetime" + } + } } } } diff --git a/lexicons/tools/ozone/report/getAssignments.json b/lexicons/tools/ozone/report/getAssignments.json new file mode 100644 index 00000000..55bf7ddb --- /dev/null +++ b/lexicons/tools/ozone/report/getAssignments.json @@ -0,0 +1,65 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.getAssignments", + "defs": { + "main": { + "type": "query", + "description": "Get assignments for reports.", + "parameters": { + "type": "params", + "properties": { + "onlyActive": { + "type": "boolean", + "default": true, + "description": "When true, only returns active assignments." + }, + "reportIds": { + "type": "array", + "items": { + "type": "integer" + }, + "maxLength": 50, + "description": "If specified, returns assignments for these reports only." + }, + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "maxLength": 50, + "description": "If specified, returns assignments for these moderators only." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["assignments"], + "properties": { + "cursor": { + "type": "string" + }, + "assignments": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.report.defs#assignmentView" + } + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/getHistoricalStats.json b/lexicons/tools/ozone/report/getHistoricalStats.json new file mode 100644 index 00000000..0d8cd670 --- /dev/null +++ b/lexicons/tools/ozone/report/getHistoricalStats.json @@ -0,0 +1,71 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.getHistoricalStats", + "defs": { + "main": { + "type": "query", + "description": "Get historical daily report statistics. Returns a paginated list of daily stat snapshots, newest first. Filter by queue, moderator, or report type.", + "parameters": { + "type": "params", + "properties": { + "queueId": { + "type": "integer", + "description": "Filter stats by queue. Use -1 for unqueued reports." + }, + "moderatorDid": { + "type": "string", + "format": "did", + "description": "Filter stats by moderator DID." + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Filter stats by report types." + }, + "startDate": { + "type": "string", + "format": "datetime", + "description": "Earliest date to include (inclusive)." + }, + "endDate": { + "type": "string", + "format": "datetime", + "description": "Latest date to include (inclusive)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 30, + "description": "Maximum number of entries to return." + }, + "cursor": { + "type": "string", + "description": "Pagination cursor." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["stats"], + "properties": { + "stats": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.report.defs#historicalStats" + } + }, + "cursor": { + "type": "string" + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/getLatestReport.json b/lexicons/tools/ozone/report/getLatestReport.json new file mode 100644 index 00000000..83357543 --- /dev/null +++ b/lexicons/tools/ozone/report/getLatestReport.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.getLatestReport", + "defs": { + "main": { + "type": "query", + "description": "Get the most recent report.", + "parameters": { + "type": "params", + "properties": {} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["report"], + "properties": { + "report": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportView" + } + } + } + }, + "errors": [ + { + "name": "NotFound", + "description": "No report found." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/report/getLiveStats.json b/lexicons/tools/ozone/report/getLiveStats.json new file mode 100644 index 00000000..80a05fd3 --- /dev/null +++ b/lexicons/tools/ozone/report/getLiveStats.json @@ -0,0 +1,45 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.getLiveStats", + "defs": { + "main": { + "type": "query", + "description": "Get live report statistics from the past 24 hours. Filter by queue, moderator, or report type. Omit all parameters for aggregate stats.", + "parameters": { + "type": "params", + "properties": { + "queueId": { + "type": "integer", + "description": "Filter stats by queue. Use -1 for unqueued reports." + }, + "moderatorDid": { + "type": "string", + "format": "did", + "description": "Filter stats by moderator DID." + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Filter stats by report types." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["stats"], + "properties": { + "stats": { + "type": "ref", + "ref": "tools.ozone.report.defs#liveStats", + "description": "Statistics for the requested filter." + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/getReport.json b/lexicons/tools/ozone/report/getReport.json new file mode 100644 index 00000000..709e93e4 --- /dev/null +++ b/lexicons/tools/ozone/report/getReport.json @@ -0,0 +1,33 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.getReport", + "defs": { + "main": { + "type": "query", + "description": "Get details about a single moderation report by ID.", + "parameters": { + "type": "params", + "required": ["id"], + "properties": { + "id": { + "type": "integer", + "description": "The ID of the report to retrieve." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportView" + } + }, + "errors": [ + { + "name": "NotFound", + "description": "No report found." + } + ] + } + } +} diff --git a/lexicons/tools/ozone/report/listActivities.json b/lexicons/tools/ozone/report/listActivities.json new file mode 100644 index 00000000..e5eaa7de --- /dev/null +++ b/lexicons/tools/ozone/report/listActivities.json @@ -0,0 +1,48 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.listActivities", + "defs": { + "main": { + "type": "query", + "description": "List all activities for a report, sorted most-recent-first.", + "parameters": { + "type": "params", + "required": ["reportId"], + "properties": { + "reportId": { + "type": "integer", + "description": "ID of the report whose activities to list" + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["activities"], + "properties": { + "activities": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportActivityView" + } + }, + "cursor": { + "type": "string" + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/queryReports.json b/lexicons/tools/ozone/report/queryReports.json new file mode 100644 index 00000000..e15da333 --- /dev/null +++ b/lexicons/tools/ozone/report/queryReports.json @@ -0,0 +1,120 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.queryReports", + "defs": { + "main": { + "type": "query", + "description": "View moderation reports. Reports are individual instances of content being reported, as opposed to subject statuses which aggregate reports at the subject level.", + "parameters": { + "type": "params", + "required": ["status"], + "properties": { + "queueId": { + "type": "integer", + "description": "Filter by queue ID. Use -1 for unassigned reports." + }, + "reportTypes": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Filter by report types (fully qualified string in the format of com.atproto.moderation.defs#reason)." + }, + "status": { + "type": "string", + "knownValues": [ + "open", + "closed", + "escalated", + "queued", + "assigned" + ], + "description": "Filter by report status." + }, + "subject": { + "type": "string", + "format": "uri", + "description": "Filter by subject DID or AT-URI." + }, + "did": { + "type": "string", + "format": "did", + "description": "Filter to reports where the subject is this DID or any record owned by this DID. Unlike `subject` (which scopes to a specific account or record), this returns all reports tied to the DID across both account-level and record-level subjects." + }, + "subjectType": { + "type": "string", + "description": "If specified, reports of the given type (account or record) will be returned.", + "knownValues": ["account", "record"] + }, + "collections": { + "type": "array", + "maxLength": 20, + "description": "If specified, reports where the subject belongs to the given collections will be returned. When subjectType is set to 'account', this will be ignored.", + "items": { + "type": "string", + "format": "nsid" + } + }, + "reportedAfter": { + "type": "string", + "format": "datetime", + "description": "Retrieve reports created after a given timestamp" + }, + "reportedBefore": { + "type": "string", + "format": "datetime", + "description": "Retrieve reports created before a given timestamp" + }, + "isMuted": { + "type": "boolean", + "default": false, + "description": "Filter by muted status. true returns only muted reports, false returns only unmuted reports. Defaults to false." + }, + "assignedTo": { + "type": "string", + "format": "did", + "description": "Filter by the DID of the moderator permanently assigned to the report." + }, + "sortField": { + "type": "string", + "default": "createdAt", + "enum": ["createdAt", "updatedAt"] + }, + "sortDirection": { + "type": "string", + "default": "desc", + "enum": ["asc", "desc"] + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { + "type": "string" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["reports"], + "properties": { + "cursor": { + "type": "string" + }, + "reports": { + "type": "array", + "items": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportView" + } + } + } + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/reassignQueue.json b/lexicons/tools/ozone/report/reassignQueue.json new file mode 100644 index 00000000..39166543 --- /dev/null +++ b/lexicons/tools/ozone/report/reassignQueue.json @@ -0,0 +1,66 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.reassignQueue", + "defs": { + "main": { + "type": "procedure", + "description": "Manually reassign a report to a different queue (or unassign it). Records a queueActivity entry on the report.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["reportId", "queueId"], + "properties": { + "reportId": { + "type": "integer", + "description": "ID of the report to reassign" + }, + "queueId": { + "type": "integer", + "description": "Target queue ID. Use -1 to unassign from any queue." + }, + "comment": { + "type": "string", + "description": "Optional moderator-only note recorded on the resulting queueActivity as internalNote." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["report"], + "properties": { + "report": { + "type": "ref", + "ref": "tools.ozone.report.defs#reportView" + } + } + } + }, + "errors": [ + { + "name": "ReportNotFound", + "description": "No report exists with the given reportId" + }, + { + "name": "ReportClosed", + "description": "The report is closed and cannot be reassigned" + }, + { + "name": "AlreadyInTargetQueue", + "description": "The report is already assigned to the target queue" + }, + { + "name": "QueueNotFound", + "description": "No active queue exists with the given queueId" + }, + { + "name": "QueueDisabled", + "description": "The target queue is disabled and cannot receive new assignments" + } + ] + } + } +} diff --git a/lexicons/tools/ozone/report/refreshStats.json b/lexicons/tools/ozone/report/refreshStats.json new file mode 100644 index 00000000..126827a0 --- /dev/null +++ b/lexicons/tools/ozone/report/refreshStats.json @@ -0,0 +1,41 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.refreshStats", + "defs": { + "main": { + "type": "procedure", + "description": "Recompute report statistics for a date range. Useful for backfilling after failures or data corrections.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["startDate", "endDate"], + "properties": { + "startDate": { + "type": "string", + "description": "Start date for recomputation, inclusive (YYYY-MM-DD)." + }, + "endDate": { + "type": "string", + "description": "End date for recomputation, inclusive (YYYY-MM-DD)." + }, + "queueIds": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "Optional list of queue IDs to recompute. Omit to recompute all groups." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": {} + } + } + } + } +} diff --git a/lexicons/tools/ozone/report/unassignModerator.json b/lexicons/tools/ozone/report/unassignModerator.json new file mode 100644 index 00000000..5f12019f --- /dev/null +++ b/lexicons/tools/ozone/report/unassignModerator.json @@ -0,0 +1,36 @@ +{ + "lexicon": 1, + "id": "tools.ozone.report.unassignModerator", + "defs": { + "main": { + "type": "procedure", + "description": "Remove report assignment.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["reportId"], + "properties": { + "reportId": { + "type": "integer", + "description": "The ID of the report to unassign." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "tools.ozone.report.defs#assignmentView" + } + }, + "errors": [ + { + "name": "InvalidAssignment", + "description": "The report ID is invalid." + } + ] + } + } +}