Skip to content

feat(activitypub,mastodon-api): thread replies and hydrate boosts for local posts - #424

Merged
davidwkeith merged 3 commits into
mainfrom
claude/anglesite-feature-gaps-8qmd6r
Jul 24, 2026
Merged

feat(activitypub,mastodon-api): thread replies and hydrate boosts for local posts#424
davidwkeith merged 3 commits into
mainfrom
claude/anglesite-feature-gaps-8qmd6r

Conversation

@davidwkeith

Copy link
Copy Markdown
Owner

Summary

First of the follow-up Anglesite-gap increments: closes the two Mastodon read-surface fidelity gaps that off-the-shelf clients hit, for the case the actor DO can answer locally.

A new pure-SQL primitive #resolveLocalObject(iri) looks a post up by its AS2 object IRI — owner outbox (source 1) first, then inbox (source 0) — with no outbound fetch. At read time the DO uses it to resolve each entry's cross-references, which the adapter threads into new optional BackendEntry.inReplyTo/BackendEntry.boost fields, and statusEntity consumes:

  • in_reply_to_id / in_reply_to_account_id — a reply whose inReplyTo names a locally-held post now carries that post's snowflake and its author (the owner account when it's a reply to the owner's own post). Previously always null.
  • Bare-IRI Announce hydration — a boost whose object is a bare IRI pointing at a locally-held post now renders its reblog with the real content and author, instead of a content-less Status.

Targets the DO does not hold still degrade to null (reply) or a content-less reblog (boost), exactly as before — dereferencing and caching a remote object is the remaining increment (the same network-fetch shape as actor-profile hydration), and the spec's Known-gaps section now says so.

Packages affected

@dwk/activitypub, @dwk/mastodon-api

Checklist

  • Read the relevant spec(s) under spec/packages/ and updated them if
    behaviour changed
  • Added/updated colocated tests (src/*.test.ts) — entity-level in @dwk/mastodon-api, DO-level (real #resolveLocalObject SQL) in @dwk/activitypub
  • Ran the local CI gate: pnpm lint && pnpm format:check && pnpm typecheck && pnpm build && pnpm test (2,803 tests green)
  • Added a changeset (pnpm changeset) if this touches a publishable
    package
  • Updated catalog.json / conformance/status.json if this adds a new
    mountable worker or changes conformance status — not applicable, no new worker and no conformance-status change

🤖 Generated with Claude Code

https://claude.ai/code/session_01XT45PcE3pgXgsQffQDWdhW


Generated by Claude Code

… local posts

Resolve two Mastodon read-surface fidelity gaps against posts the actor
DO already holds, via a new pure-SQL #resolveLocalObject (owner outbox
then inbox, never an outbound fetch):

- A reply whose inReplyTo names a locally-held post now carries that
  post's snowflake as in_reply_to_id and its author as
  in_reply_to_account_id (the owner account for a reply to the owner's
  own post).
- A bare-IRI Announce of a locally-held post now hydrates its reblog
  with the real content and author instead of rendering content-less.

Targets the DO does not hold still degrade to null / content-less as
before; dereferencing a remote object is the remaining increment. New
optional BackendEntry.inReplyTo/boost fields thread the resolution
through the adapter into statusEntity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT45PcE3pgXgsQffQDWdhW

Copy link
Copy Markdown
Owner Author

The github-advanced-security failure is the same GitHub-side infra issue seen on #423, not something in this diff: the Copilot code-scanning agent aborts before analysis with CAPIError: 400 model_not_supported (its configured sweagent-capi:claude-opus-4.6 is rejected by Copilot's own API). Nothing pushable fixes it; a re-run once GitHub's side recovers should clear it.

The repo's own gates are green on this head — release-gate ✅, integration ✅, Analyze (actions) ✅ — with build-test and the JS/TS CodeQL analysis still running. I'll keep watching.


Generated by Claude Code

@davidwkeith davidwkeith left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff, spec/packages/mastodon-api.md, and the new tests. The #resolveLocalObject SQL and the DO-level plumbing (object.ts, mastodon-api.ts) look solid and consistent with existing patterns in the file (verify_state IS NOT 'failed' AND removed_at IS NULL, this.#config!, etc.), and it's applied uniformly to both the timeline and notifications reads plus the single-entry endpoint. Spec and changeset are updated appropriately.

One correctness issue worth fixing before merge (inline comment below): the new ownerAccount-dependent branch in statusEntity for in_reply_to_account_id (and the equivalent for a boosted owner post) isn't reliably wired up from the three route handlers, so the id resolution silently degrades to a synthesized remote-account id in the most common real path (mention notifications). Recommend fixing the three call sites' ownerAccount gating, plus a handler-level test exercising authorIsOwner: true through notifications.ts at minimum, since that's the gap none of the current tests catch.

CONTRIBUTING.md conformance

  • PR title: feat(activitypub,mastodon-api): thread replies and hydrate boosts for local posts — correct Conventional Commits form (lowercase type, comma-separated scope, no trailing period).
  • Commit message matches the same convention and is well-described.
  • PR body uses the template's Summary / Packages affected / Checklist headings verbatim, with the one inapplicable item left unchecked with a reason rather than deleted. ✅.
  • Spec (spec/packages/mastodon-api.md) updated in the same PR to reflect the new behavior and remaining gap. ✅.
  • Changeset added for both affected publishable packages (@dwk/activitypub, @dwk/mastodon-api), minor bump. ✅.
  • Colocated tests added in all three touched packages' src/*.test.ts. ✅ (see inline comment for a gap in what they cover).

No other conformance issues found.


Generated by Claude Code

Comment thread packages/mastodon-api/src/entities.ts
… owner

The statusEntity in_reply_to_account_id / reblog owner branches only
resolved to the real owner id when a route handler passed ownerAccount,
but the three call sites gated that on the entry's own source === 1 — so
a mention notification for a reply to the owner's post (the primary
reply-threading path) always fell through to a synthesized r_... id, and
notifications.ts never fetched ownerAccount at all.

Add entryNeedsOwnerAccount (source 1, or replies to / boosts an owner
post) and gate all three handlers on it; thread ownerAccount through
notificationEntity into the mention's status. Handler-level tests through
notifications.ts and statuses.ts now exercise authorIsOwner: true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT45PcE3pgXgsQffQDWdhW

@davidwkeith davidwkeith left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass.

CONTRIBUTING.md conformance: checked out the branch, ran a full repo pnpm build (needed since @dwk/mastodon-api/@dwk/activitypub consume sibling workspace packages through their dist/ exports maps), then the local CI gate scoped to both touched packages — pnpm --filter @dwk/activitypub typecheck, pnpm --filter @dwk/mastodon-api typecheck, pnpm test --project @dwk/activitypub (291/291 passing), pnpm test --project @dwk/mastodon-api (157/157 passing), plus repo-wide pnpm lint/pnpm format:check. All clean. PR title, changeset, and spec updates (spec/packages/mastodon-api.md Known-gaps section) follow the required conventions, and the unchecked catalog checklist item has a valid reason.

Correctness: traced #resolveLocalObject's two SQL paths (owner outbox, then inbox with the existing removed_at/verify_state tombstone filters — outbox has no such column, but outbox rows are hard-deleted rather than soft-tombstoned elsewhere in this file, so that's consistent) and the entryNeedsOwnerAccount wiring across statuses.ts/timelines.ts/notifications.ts. The regression the tests specifically call out (notifications.ts not threading ownerAccount into notificationEntity) is fixed and covered. Left one inline note on a minor, non-blocking fidelity gap in the boost/reply author's cached profile enrichment.


Generated by Claude Code

Comment thread packages/mastodon-api/src/entities.ts
… cache

The hydrated boost's nested reblog reused the outer entry's actorProfiles
(booster + relayedBy), so the boosted post author's cached profile was
never included and the reblog account rendered via the IRI-derived
fallback. Thread the resolved boost/reply author IRIs through
#clientActorProfiles — a free DO-local cache read (same budget as
#resolveLocalObject) — so a cached profile enriches the nested account
when one exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XT45PcE3pgXgsQffQDWdhW

@davidwkeith davidwkeith left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

Closes two Mastodon read-surface fidelity gaps for locally-held targets: in_reply_to_id/in_reply_to_account_id threading, and bare-IRI Announce (boost) hydration. Both are resolved read-time via a new pure-SQL #resolveLocalObject(iri) (owner outbox, then inbox — never an outbound fetch), threaded through BackendEntry.inReplyTo/.boost into statusEntity.

Code quality / correctness

  • #resolveLocalObject correctly checks outbox before inbox (so a reply/boost of the owner's own post resolves to the owner, not a stale inbox copy of the same object), and filters removed_at IS NULL AND verify_state IS NOT 'failed' on the inbox side — consistent with the existing filtering convention used elsewhere in this file (e.g. the pre-existing #clientInteractions query at line ~1834 uses the same verify_state IS NOT 'failed' pattern), so this isn't introducing a new convention.
  • entryNeedsOwnerAccount correctly widens the existing source === 1 check (used in timelines.ts/statuses.ts/notifications.ts) to also cover inReplyTo.authorIsOwner/boost.authorIsOwner — and the notifications.test.ts regression test explicitly calls out that this was a review-flagged bug (reply to the owner's post previously resolving to a synthesized r_... id instead of the real owner id). Good catch, well tested.
  • statusEntity's boost-hydration branch recurses into itself to build the nested reblog Status from the resolved object — clean reuse, and correctly keeps the outer Announce shell content-less (matching Mastodon's boost shape) while the reblog field carries the real content/author.
  • One thing worth a second look, not necessarily a blocker: #resolveLocalObject runs a json_extract(json, '$.object.id') = ? scan over the full outbox/inbox tables (no index on that expression) once per entry that needs resolution (#clientResolved is called per row in the notifications/timeline listing loop). This mirrors the existing #clientInteractions per-entry-scan pattern already in this file, so it's not a new class of problem introduced here — but as both patterns compound on the same hot paths, it may be worth a follow-up to add an index (or a generated column) on json_extract(json,'$.object.id')/$.object.inReplyTo if actor DOs start holding non-trivial outbox/inbox row counts, per the runtime-budget NFR.

CONTRIBUTING.md conformance

  • ✅ PR title feat(activitypub,mastodon-api): thread replies and hydrate boosts for local posts — correct Conventional Commits scope/format.
  • ✅ PR body keeps Summary / Packages affected / Checklist headings verbatim.
  • ✅ Changeset present (.changeset/mastodon-reply-boost-fidelity.md), bumping both affected packages minor.
  • catalog.json/conformance/status.json checkbox left unchecked with a one-line reason ("not applicable, no new worker and no conformance-status change") — correct, matches CONTRIBUTING's "leave unchecked with reason" rule.
  • ✅ Spec updated: spec/packages/mastodon-api.md's "Known gaps" section rewritten to describe the new local-resolution behavior and the remaining remote-dereference gap.
  • ✅ Colocated tests: DO-level tests against real #resolveLocalObject SQL in mastodon-api.test.ts (reply-to-owner, orphan reply, bare-IRI boost hydration, cached-profile enrichment), entity-level tests in entities.test.ts/notifications.test.ts/statuses.test.ts.
  • Noted your comment on the PR already explaining the github-advanced-security failure as the same recurring GitHub-side infra issue (model_not_supported) seen on #423, unrelated to this diff.

Test coverage

Thorough — covers the happy path for both reply threading and boost hydration, the "target not held locally" degrade-gracefully path, and the actor-profile enrichment side effect. No gaps found.

No blocking issues.


Generated by Claude Code

@davidwkeith
davidwkeith merged commit 07fc404 into main Jul 24, 2026
8 of 9 checks passed
@davidwkeith
davidwkeith deleted the claude/anglesite-feature-gaps-8qmd6r branch July 24, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants