Skip to content

Implement ActivityPub discovery endpoints#33

Merged
sij411 merged 10 commits into
fedify-dev:nextfrom
sij411:feat/ap-discovery
Jul 3, 2026
Merged

Implement ActivityPub discovery endpoints#33
sij411 merged 10 commits into
fedify-dev:nextfrom
sij411:feat/ap-discovery

Conversation

@sij411

@sij411 sij411 commented Jul 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Add WebFinger discovery for the configured local actor.
  • Serve the configured local Actor at /users/{username} as application/activity+json.
  • Serve a seeded public Note at /notes/{id} as application/activity+json.
  • Add route tests for WebFinger, Actor, and Note success/error cases.

Design Notes

This PR adds the public discovery/read surface for the server runtime, but does not introduce durable runtime storage yet. The public Note endpoint uses a minimal in-memory seeded note to prove canonical URL routing and feder-vocab serialization before storage exists.

Durable storage, state restoration, and action execution are intentionally left to #25.

Validation

  • mise run check
  • mise run test
  • Manual curl verification for /notes/1

Closes #23

Summary by CodeRabbit

  • New Features

    • Added ActivityPub-style endpoints for user profiles, notes, and WebFinger discovery.
    • Local server setup now includes a default actor profile and a preview note.
    • Added a convenient test task to run the Rust test suite.
  • Bug Fixes

    • Requests for unknown users or notes now return clear not-found responses.
    • Missing WebFinger lookup data now returns a bad-request response.
  • Tests

    • Added coverage for successful and failing responses across the new endpoints.

sij411 added 8 commits June 30, 2026 16:09
Assisted-by: Codex:gpt-5.5
Expose GET /users/{username} from the server runtime and return the
  configured local actor as application/activity+json. Keep the local actor
  in
  AppState and pass a clone into FederCore so HTTP serving does not need to
  lock
  the core state.
  Cover the configured local actor route, unknown usernames, response
  content
  type, and serialized ActivityPub actor fields.

  Assisted-by: Codex:gpt-5.5
  Cover the seeded public Note route, unknown Note IDs, response content
  type,
  and serialized ActivityPub Note fields.

  Assisted-by: Codex:gpt-5.5
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23df0624-d009-4e6c-a05e-cb37e3f1f185

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements ActivityPub discovery endpoints in feder-runtime-server: WebFinger resolution, actor document retrieval, and public note retrieval. AppState and RuntimeConfig are extended with identity/storage fields, new routes are wired, dependencies added, and a mise test task introduced.

Changes

ActivityPub Endpoints and Runtime Wiring

Layer / File(s) Summary
Runtime identity config and AppState wiring
crates/feder-runtime-server/src/config.rs, crates/feder-runtime-server/src/app.rs
RuntimeConfig adds username, preferred_username, handle_host, note_id with defaults in default_local(); AppState adds local_actor, username, handle_host, notes, seeded in from_config, and new GET routes are registered in build_router.
Actor endpoint
crates/feder-runtime-server/src/actor.rs, crates/feder-runtime-server/src/lib.rs
New actor handler returns the local actor as application/activity+json for matching usernames or 404 otherwise; module exposed publicly with tests for both outcomes.
WebFinger endpoint
crates/feder-runtime-server/src/webfinger.rs
New webfinger handler with WebFingerQuery/WebFingerResponse/WebFingerLink types validates the resource query param, returning 400, 404, or 200 with application/jrd+json; includes tests.
Note retrieval endpoint
crates/feder-runtime-server/src/note.rs
New note handler looks up a seeded note by canonical ID matching handle_host, returning application/activity+json or 404; includes tests.
Dependencies and tooling
crates/feder-runtime-server/Cargo.toml, mise.toml
Adds serde, serde_json, and tower dependencies; adds a mise task to run cargo test.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router as AxumRouter
  participant WebFingerHandler
  participant ActorHandler
  participant NoteHandler
  participant State as AppState

  Client->>Router: GET /.well-known/webfinger?resource=...
  Router->>WebFingerHandler: dispatch(state, query)
  WebFingerHandler->>State: read username, local_actor
  WebFingerHandler-->>Client: 200 jrd+json / 400 / 404

  Client->>Router: GET /users/username
  Router->>ActorHandler: dispatch(state, username)
  ActorHandler->>State: compare username, read local_actor
  ActorHandler-->>Client: 200 activity+json / 404

  Client->>Router: GET /notes/id
  Router->>NoteHandler: dispatch(state, id)
  NoteHandler->>State: search notes by canonical id
  NoteHandler-->>Client: 200 activity+json / 404
Loading

Possibly related PRs

  • fedify-dev/feder#28: Extends the same feder-runtime-server foundation (AppState fields/routes, RuntimeConfig::default_local()) that this PR builds upon.

Suggested labels: enhancement

Suggested reviewers: dahlia

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The new mise test task is unrelated support work that falls outside the endpoint implementation scope. Remove the mise task addition unless it is explicitly required, or document why it belongs in this PR.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding ActivityPub discovery endpoints.
Linked Issues check ✅ Passed The PR implements WebFinger, actor, and note endpoints with tests and config-derived IDs/content types, matching the linked issue scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sij411

sij411 commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6cc594f510

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/feder-runtime-server/src/note.rs Outdated
@sij411

sij411 commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
crates/feder-runtime-server/Cargo.toml (1)

15-19: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider workspace-managed version for tower like the other dependencies.

serde and serde_json use .workspace = true, but tower = "0.5" is pinned locally, inconsistent with the workspace dependency management style used elsewhere in this file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/feder-runtime-server/Cargo.toml` around lines 15 - 19, The dependency
declaration for tower is inconsistent with the workspace-managed style used for
serde and serde_json. Update the dev-dependencies entry in Cargo.toml for tower
to use the workspace version management pattern instead of a locally pinned
version, matching the existing dependency declarations in this manifest.
crates/feder-runtime-server/src/config.rs (1)

20-30: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Host/port now duplicated across three config representations.

handle_host duplicates the host:port that's already embedded in bind (as SocketAddr) and in actor_id/inbox/outbox/note_id (as Iris). Nothing enforces these stay consistent — e.g. changing actor_id's host without updating handle_host would silently break WebFinger (Line 54 in webfinger.rs constructs acct:{username}@{handle_host}) and the note lookup (note.rs Line 33), even though the actor/note IDs themselves would still resolve correctly.

Consider deriving handle_host from actor_id's authority instead of storing it independently, or adding a debug assertion/validation in default_local() (and future config loaders) that the host segments agree.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/feder-runtime-server/src/config.rs` around lines 20 - 30,
RuntimeConfig currently stores handle_host separately even though the host:port
is already present in bind and the IRI authorities for
actor_id/inbox/outbox/note_id, which can drift out of sync. Update
RuntimeConfig/default_local() to derive handle_host from actor_id’s authority
(or otherwise validate that these host segments match when loading config) so
WebFinger’s acct:{username}@{handle_host} and note lookup always use the same
host as the actor/note IDs.
crates/feder-runtime-server/src/app.rs (2)

25-33: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

No cross-validation between actor_id, username, and handle_host.

from_config independently derives the actor's id from config.actor_id, but the /users/{username} route (in actor.rs) matches on config.username, and WebFinger's subject/alias (in webfinger.rs) is built from config.username + config.handle_host. Nothing here enforces that actor_id's path segment actually corresponds to username, or that its host matches handle_host. With the shipped defaults these are consistent, but a future config change to any one of these fields independently (e.g. renaming the account) would silently desynchronize WebFinger's advertised actor URL from what /users/{username} can actually serve, without any error at startup.

Consider deriving actor_id/inbox/outbox from username+handle_host (or asserting consistency) in from_config.

Also applies to: 36-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/feder-runtime-server/src/app.rs` around lines 25 - 33,
`AppState::from_config` currently builds `actor_id`, `inbox`, and `outbox`
independently from config values that are also used by `/users/{username}` and
WebFinger, so add a consistency check or derive all actor URLs from `username` +
`handle_host` in one place. Update `from_config` to assert that
`config.actor_id`’s path segment matches `config.username` and its host matches
`config.handle_host`, or replace the separate construction with a single
canonical builder used by `AppState`, `actor.rs`, and `webfinger.rs` so the
advertised actor URL cannot drift from the served route.

26-33: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

notes/local_actor cloned per-request unlike core.

AppState wraps core in Arc<Mutex<..>> for cheap cloning, but local_actor: Actor and notes: Vec<Note> are plain fields cloned on every State<AppState> extraction (i.e. on every request, including unrelated routes like /healthz). Negligible today with a single seeded note, but inconsistent with the Arc pattern already used for core, and will matter once notes grows per TODO(#25).

Also applies to: 54-60

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/feder-runtime-server/src/app.rs` around lines 26 - 33, `AppState`
currently clones `local_actor: Actor` and `notes: Vec<Note>` on every
`State<AppState>` extraction, unlike `core` which is cheap-cloned via
`Arc<Mutex<FederCore>>`. Update `AppState` and its use in the request handlers
to store `local_actor` and `notes` behind shared ownership (matching the `core`
pattern) so unrelated routes do not duplicate them per request. Focus on the
`AppState` struct and any constructor/handler code that accesses `local_actor`
or `notes`, especially around the routes mentioned in the review.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/feder-runtime-server/Cargo.toml`:
- Line 19: The `tower` dependency is missing the `util` feature, so
`ServiceExt::oneshot` used by the tests will not be available. Update the
`tower` entry in `Cargo.toml` for `crates/feder-runtime-server` to enable
`features = ["util"]`, keeping the dependency version at 0.5 so the
`tower::ServiceExt` import resolves correctly.

In `@crates/feder-runtime-server/src/note.rs`:
- Around line 25-42: The note lookup in note() is re-deriving the canonical URL
with a hardcoded http scheme, which can fail when the configured note id uses a
different scheme. Update the lookup logic to compare against the note’s existing
id value directly instead of rebuilding it from app_state.handle_host, and keep
the change localized to the note() function so it matches the config.rs contract
for note_id.

---

Nitpick comments:
In `@crates/feder-runtime-server/Cargo.toml`:
- Around line 15-19: The dependency declaration for tower is inconsistent with
the workspace-managed style used for serde and serde_json. Update the
dev-dependencies entry in Cargo.toml for tower to use the workspace version
management pattern instead of a locally pinned version, matching the existing
dependency declarations in this manifest.

In `@crates/feder-runtime-server/src/app.rs`:
- Around line 25-33: `AppState::from_config` currently builds `actor_id`,
`inbox`, and `outbox` independently from config values that are also used by
`/users/{username}` and WebFinger, so add a consistency check or derive all
actor URLs from `username` + `handle_host` in one place. Update `from_config` to
assert that `config.actor_id`’s path segment matches `config.username` and its
host matches `config.handle_host`, or replace the separate construction with a
single canonical builder used by `AppState`, `actor.rs`, and `webfinger.rs` so
the advertised actor URL cannot drift from the served route.
- Around line 26-33: `AppState` currently clones `local_actor: Actor` and
`notes: Vec<Note>` on every `State<AppState>` extraction, unlike `core` which is
cheap-cloned via `Arc<Mutex<FederCore>>`. Update `AppState` and its use in the
request handlers to store `local_actor` and `notes` behind shared ownership
(matching the `core` pattern) so unrelated routes do not duplicate them per
request. Focus on the `AppState` struct and any constructor/handler code that
accesses `local_actor` or `notes`, especially around the routes mentioned in the
review.

In `@crates/feder-runtime-server/src/config.rs`:
- Around line 20-30: RuntimeConfig currently stores handle_host separately even
though the host:port is already present in bind and the IRI authorities for
actor_id/inbox/outbox/note_id, which can drift out of sync. Update
RuntimeConfig/default_local() to derive handle_host from actor_id’s authority
(or otherwise validate that these host segments match when loading config) so
WebFinger’s acct:{username}@{handle_host} and note lookup always use the same
host as the actor/note IDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4de8e3d0-1a4f-4188-9648-7605841dcf10

📥 Commits

Reviewing files that changed from the base of the PR and between b8fe0ab and 6cc594f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • crates/feder-runtime-server/Cargo.toml
  • crates/feder-runtime-server/src/actor.rs
  • crates/feder-runtime-server/src/app.rs
  • crates/feder-runtime-server/src/config.rs
  • crates/feder-runtime-server/src/lib.rs
  • crates/feder-runtime-server/src/note.rs
  • crates/feder-runtime-server/src/webfinger.rs
  • mise.toml

Comment thread crates/feder-runtime-server/Cargo.toml Outdated
Comment thread crates/feder-runtime-server/src/note.rs
@sij411

sij411 commented Jul 3, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 0cc162c0bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sij411 sij411 merged commit ccc1623 into fedify-dev:next Jul 3, 2026
5 checks passed
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.

1 participant