Skip to content

Support consent-respecting quote posts (FEP-044f) #27

Description

@dahlia

BotKit's quote support predates any consent mechanism. When a bot quotes a message, Session.publish() emits the legacy quoteUrl property together with a Misskey-style Link tag (rel: https://misskey-hub.net/ns#_misskey_quote) and an HTML fallback, then delivers the Create right away. In the other direction, the Create handler fires onQuote whenever an incoming message references one of the bot's messages through those properties. Nobody asks anyone for permission at any point.

FEP-044f changes that. It defines a quote property for representing quote posts, an advisory quote policy advertised through GoToSocial's interaction policy vocabulary, and a consent handshake: the quoting actor sends a QuoteRequest activity to the quoted author, who replies with an Accept carrying a QuoteAuthorization stamp as its result, or with a Reject. The stamp is a separate dereferenceable object so the author can revoke it later by Deleteing it. Third parties treat a quote as approved only when it is a self-quote or when its quoteAuthorization property points to a valid stamp.

Mastodon 4.4 verifies and displays remote quote posts and 4.5 authors them with configurable quote policies, so quotes between BotKit bots and most of the fediverse now go through this protocol. Without it, Mastodon shows quotes of a bot's posts as unverifiable, and a bot's own quote posts never render as quotes on Mastodon at all.

What Fedify already provides

Fedify 2.3.1, the version BotKit currently pins, ships the whole FEP-044f vocabulary: the QuoteRequest and QuoteAuthorization classes, InteractionPolicy and InteractionRule with the canQuote sub-policy, interactionPolicy on Object, and quote/quoteAuthorization on the message classes next to the legacy quoteUrl. No vocabulary work is needed; everything in this issue tree is protocol flow and public API in BotKit itself.

API surface

The design mirrors the existing follow-request machinery, because the two protocols have the same shape: a request arrives, a policy either decides automatically or defers to a handler, and an Accept or Reject goes back.

A new quotePolicy option on CreateBotOptions plays the role followerPolicy plays for follows, and the same option on SessionPublishOptions overrides it per message. It accepts either a plain string ("public", "followers", "nobody", or "manual"), which covers the choices Mastodon exposes, or an { automatic, manual } object for the two-axis model GoToSocial defines and the FEP adopts. The policy is advertised on every outgoing message as interactionPolicy.canQuote and drives the automatic handling of incoming QuoteRequest activities.

A QuoteRequest interface modeled after FollowRequest is handed to a new onQuoteRequest event handler, with accept() and reject() methods; accept() issues a QuoteAuthorization stamp, stores it, and serves it through a new object dispatcher. Outgoing quotes keep the current non-blocking publish() semantics: the Create is delivered immediately, a QuoteRequest follows, and when the Accept arrives BotKit attaches the stamp with an Update. New events (onQuoteAccepted, onQuoteRejected, onQuoteRevoked) report the outcome, and messages expose quoteApprovalState, quoteApproved, and a parsed quotePolicy for inspection.

The Repository interface grows required methods for storing issued stamps and for indexing received ones. This is a breaking change for third-party Repository implementations, which is acceptable before 1.0 and will be called out in the changelog.

Decisions already made

publish() does not wait for approval. Manual approval on the remote side can take days, and blocking a bot on a human decision is not workable; the FEP explicitly supports the create-then-update sequence for this case. Servers that never answer (the Misskey family does not implement the FEP) leave the message in the same state it is in today.

When a quote is rejected or its authorization is revoked, BotKit removes the quote from the post with an Update by default rather than deleting the whole post. Removing only the quote respects the author's refusal while preserving the bot's own words; a bot that prefers deletion can call message.delete() from the corresponding event handler.

The BotKit-level QuoteRequest interface intentionally shares its name with the Fedify vocabulary class, following the FollowRequest/Follow precedent. BotKit does not re-export Fedify's vocabulary, so there is no export conflict.

Plan

The work splits into three sub-issues, to be done in order and released together in 0.5.0. Inbound support comes first because it is self-contained and it is what lets Mastodon users quote a bot's posts at all.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Fields

Priority

None yet

Effort

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions