Repository navigation
feat(activitypub): follow a followed account's Mastodon Move - #579
Conversation
When an account this actor follows sends a Move, queue a `move` row; the alarm fetches the target and, only if its alsoKnownAs lists the mover, follows the target and sends the mover an Undo(Follow). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015LRGjmHE99WESN5HBERxSj
davidwkeith
left a comment
There was a problem hiding this comment.
Review against CONTRIBUTING.md / CLAUDE.md.
Conforms: Conventional Commits title with a valid scope, PR template headings kept verbatim, a minor changeset for @dwk/activitypub, the spec updated, and the unchecked catalog.json item carries a reason. The design is sound: the target fetch runs from the alarm, the alsoKnownAs check matches Mastodon, and the target goes through the SSRF-safe #resolveInbox.
Suggested fixes (none blocking):
-
Missing test coverage for behaviour the spec and PR body promise. The four tests cover the happy path, a missing alias, an unfollowed mover and a mismatched
object. Nothing covers:- an owner unfollow before the alarm cancelling the queued move (
#pendingAcceptStillActive,kind === "move"); - the "at most one
moverow per mover" dedupe (send the sameMovetwice with different ids); - the "skipped if already following the target" branch (
alreadyFollowing: true); - an unsafe or self
target.
Please add DO-level tests for at least the cancel and dedupe paths.
- an owner unfollow before the alarm cancelling the queued move (
-
Terminal resolve failure side effect (
object.tsalarm loop). When the target can't be resolved and attempts are exhausted, the existing code runsUPDATE following SET actor_type='Unknown' WHERE actor = row.actor. Formoverowsrow.actoris the target. If the owner already follows the target, a failed fetch will mark that rowUnknown. Skip that update forkind === "move", or confirm it's intended. -
MoveRejectedreason. Onlyalias_missingis ever logged, so an unresolvable target is silent apart from the genericDeliveryFailed. Fine to leave, but the log doc comment (log.ts) should say thatreasoncurrently has a single value. Consider also documenting both new events wherever the otheractivitypub.*events are listed (e.g. the observability spec or README), if such a list exists. -
Nit, doc formatting. In
spec/packages/activitypub.mdthe addedMovemakes the first bullet's line exceed the wrapped width. Runpnpm format:checkif it hasn't already caught this, since prose-wrap may differ in CI.
I didn't run the test suite locally.
Generated by Claude Code
Don't mark an existing follow of the migration target `Unknown` when the target can't be resolved; document MoveRejected's single reason; and add DO tests for move dedupe, owner-unfollow cancellation, an already-followed target, and self/private/non-https targets. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_015LRGjmHE99WESN5HBERxSj
Summary
Inbound
Move, the follower side of Mastodon account migration. This is the first of the SiliconBeest interop extensions;EmojiReact, quote posts and FEP-8fcf follow in separate PRs.Before this change, an inbound
Movehit thedefaultbranch and was ignored, so this actor kept following a dead account. Now:Moveis acted on only when itsactor(already bound to the HTTP signer) equals itsobject, itstargetis a different, SSRF-safe IRI, and this actor follows the mover. Anything else is accepted with202and ignored.pending_acceptrow of kindmove. The target fetch runs from the alarm, so the inbound POST never holds the DO's input gate on an outbound request. TheFollowandUndo(Follow)are built at enqueue time, while the forwarded config is available.alsoKnownAsto list the mover, the same check Mastodon applies. If it does, this actor follows the target (apendingfollowingrow plus aFollow, skipped if it already follows the target), drops the mover's row, and sends the mover anUndo(Follow)(Mastodon matches that on actor and object, so the originalFollowid isn't needed). If it doesn't, nothing changes andactivitypub.move.rejectedis logged.moverow is queued per mover. An unreachable target is retried like any other resolution, and it never marks an existing follow of the target asUnknown.#resolveInboxnow also returns the actor'salsoKnownAs.Moving off this actor (
movedToand an outboundMove) is still out of scope, as the spec already states.Packages affected
@dwk/activitypub
Checklist
spec/packages/and updated them ifbehaviour changed:
spec/packages/activitypub.mdS2S sectionsrc/*.test.ts), DO-level inindex.test.ts: a verified move follows the target and unfollows the mover; a target without the alias changes nothing; aMovefrom an account we don't follow is ignored; aMovewhoseobjectisn't the mover is ignored; twoMoves queue one row and fetch the target once; an owner unfollow before the alarm cancels the move without fetching; an already-followed target only gets the mover'sUndo; self, private-network and non-https targets are ignoredpnpm lint && pnpm format:check && pnpm typecheck && pnpm build && pnpm test(3314 passed)pnpm changeset) if this touches a publishablepackage
catalog.json/conformance/status.jsonif this adds a newmountable worker or changes conformance status: not applicable, no new worker and no change to conformance status
🤖 Generated with Claude Code
https://claude.ai/code/session_015LRGjmHE99WESN5HBERxSj