feat: instance-level ActivityPub federation - #1079
Open
Flomp wants to merge 199 commits into
Open
Conversation
- TestInitInstanceActorCreatesApplicationActor: verifies actor_type=Application, preferred_username=instance, is_local=true, stable IRI, PEM public key - TestInitInstanceActorIsIdempotent: verifies single record and identical public_key after two calls - TestInitInstanceActorNameFromOrigin: verifies www. stripped from hostname in username field - TestInstanceActorJSONShape: verifies AP JSON map keys via buildInstanceActorJSON helper - Tests fail to compile (RED) because InitInstanceActor and buildInstanceActorJSON are undefined
- db/migrations/1782290000_add_actor_type_to_activitypub_actors.go: adds actor_type text column to activitypub_actors (pbc_1295301207, field id text_actor_type_001), backfills existing rows to 'Person' - db/federation/instance.go: InitInstanceActor (idempotent startup, RSA keypair, Application actor), buildInstanceActorJSON helper, InstanceActorGet handler (map-based to include manuallyApprovesFollowers) - db/main.go: add pocketbase/federation import, register GET /activitypub/instance route, call federation.InitInstanceActor synchronously in initData before goroutine - db/federation/instance_test.go: updated test harness to use core.NewBaseApp with programmatic collection creation and system migrations import (avoids Meilisearch dependency) - All four RED tests now pass (GREEN)
…ce values Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.Save(collection) is what issues the ALTER TABLE. The backfill UPDATE must run after Save, not before, otherwise the column doesn't exist yet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…activitypub/instance PocketBase is not publicly accessible; the SvelteKit layer serves all /api/v1/* requests. Queries activitypub_actors directly via locals.pb, same pattern as existing user actor routes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…r docs SvelteKit route handles the public endpoint; InstanceActorGet and buildInstanceActorJSON are no longer needed in the Go layer. Also removes the PocketBase route registration and TestInstanceActorJSONShape which tested the deleted helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3 plans in 2 waves covering INST-03, FLCL-01..05: - 02-01: migration adding "rejected" to follows.status - 02-02: instance inbox endpoint + incoming Follow -> pending - 02-03: admin lifecycle hooks (Follow/Accept/Reject/Undo) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New migration 1782290001 appends "rejected" to follows.status allowed values - Idempotent up-function: skips append if value already present - Reversible down-function: filters out "rejected", restoring ["pending","accepted"] - Follows collection id: 8obn1ukumze565i, status field id: select2063623452
- SUMMARY.md for plan 02-01
- Migration 1782290001 adds "rejected" to follows.status select field
- Schema prerequisite for FLCL-04 (Reject{Follow} lifecycle) satisfied
Both the login prompt and main dashboard were visible briefly before Alpine
ran, leaking the full dashboard to unauthenticated users on slow connections.
x-cloak + an inline [x-cloak]{display:none} style rule (not Tailwind — CDN
is async) hides both sections until Alpine sets authenticated and removes the
attribute.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
x-cloak relies on an attribute-selector CSS rule that Tailwind CDN can interfere with. Using style="display:none" on <main> guarantees it is hidden before any script runs — Alpine's x-show overwrites the inline display property when authenticated=true. The login prompt has no inline style so it shows immediately as the safe default. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alpine.js v3 uses Function() constructor internally and requires 'unsafe-eval' in script-src. img-src 'none' blocked the browser's automatic favicon request; changed to 'self'. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inline logo_text_light.svg (white fills for dark background) into the header, replacing the plain text h1. Scaled to 40px height with a "Federation" label separator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Group logo+label into left div so admin link stays right-aligned. Replace inline chevron SVG with fa-gauge-high. Add FA 6 CDN and extend CSP with style-src/font-src for cdnjs.cloudflare.com. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…extension Uses the experimental UIExtensions API (v0.37.0+, discussion #7612). Embeds federation_ext/main.js which calls app.store.headerLinks.push() with the ri-share-circle-line Remix Icon and href /federation/. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Custom CSS replacing Tailwind: IBM Plex Sans, CSS vars for accent/surface/border/text, dark mode via html.dark class - Accent color read from pbSettings localStorage at paint time; --accent-fade computed from parsed hex for focus rings - Header bg: var(--accent); logo inner fills use .logo-accent-fill so mountain cutout blends into the coloured header background - Remix Icons (ri-) replace Font Awesome throughout - Admin email extracted from JWT payload shown in header - PocketBase-style table: uppercase column headers, row hover, table-footer "Total: N" bar - Pill badges with per-status/direction colour tokens - Spinner variants: white (in buttons) and accent (standalone) - Login prompt card replaces old inline prompt - CSP updated: drop Tailwind + cdnjs; add fonts.googleapis.com, fonts.gstatic.com, cdn.jsdelivr.net for style/font-src Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The colspan="4" empty-state td was caught by the last-child rule since it's both first and last child in its row. Scope the rule with :not(.state-cell) to keep it centred. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PocketBase opens /federation/ in a new tab, so navigating back is wrong — close the tab instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ctors assembleActor never mapped pubActor.Type to the actor_type field, so remote instance actors were stored with a blank actor_type. Check for ApplicationType after the remote fetch and set it explicitly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
createOutboundFollow blocked any retry when a follow record existed, regardless of status. A rejected record should be removed so the admin can send a new Follow — only pending and accepted states are genuine conflicts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a danger-themed confirmation modal (icon, domain name, description, Cancel + Disconnect actions) matching the PocketBase admin visual language. Escape and backdrop click also dismiss it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Outbound-pending rows now show a "Cancel request" button alongside
the waiting label. Reuses the disconnect endpoint (which sends
Undo·Follow) and the same confirmation modal, but with context-aware
title and body copy ("Cancel follow request?" / "Withdraw request").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous query counted all public trails including those federated from remote instances. Join against activitypub_actors and filter is_local=1 so the count reflects what this instance produces, matching fediverse convention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…JOIN buildNodeInfo21 now joins activitypub_actors to filter local trails, so the test app needs that table and an author relation on trails. Added seedLocalActor helper and wired author on seeded trail records. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Version is now injected by the SvelteKit proxy from web/package.json. Go hardcodes "dev" as a placeholder; the proxy overwrites it before the response reaches any client. Drop the two env-var version tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
localhost:8090/federation/— a standalone SPA injected into the PocketBase admin panel via the UI extension API, matching the PocketBase visual language (IBM Plex Sans, Remix Icons, accent colour theming)web/package.jsonvia the SvelteKit proxyWhat's new
Application-type AP actor created at startup; RSA keypair generated once and stored encryptedinstanceFollowerInboxes()appended to recipient list for all Create/Update/Delete activities on public contentPOST /api/v1/activitypub/instance/inbox— HTTP-sig verified, dispatches Follow/Accept/Reject/Undo/Create/Update/DeletePOST /federation/discover,/follow,/approve/:id,/reject/:id,/disconnect/:id,GET /federation/peersGET /federation/— discover, connect, manage peers, confirmation modals, status badgesis_local = 1; version injected frompackage.jsonin SvelteKit proxyTest plan
Instance actor
actor_type = instance,is_local = true, and a non-empty RSA public keyGET /api/v1/activitypub/instancereturns a valid ActivityPubApplicationobject withinbox,outbox, andpublicKeyDiscover
/federation/→ paste a valid remote Wanderer URL → "Discover" shows the remote instance domain, version, user count, and trail countFollow lifecycle
pendingfollow record appears in the peers table with "Awaiting approval…" and a "Cancel request" buttonpendingwith Approve / Reject buttonsaccepted; content starts flowingrejected; re-attempting the follow succeeds (rejected record is cleared first)Content sync
is_public = false) is never delivered to BAdmin UI
/federation/in a new tab/federation/without a superuser session shows the login prompt card, not the peer tableNodeInfo
GET /.well-known/nodeinfo/2.1→software.versionmatches the value inweb/package.jsonlocalPostscount equals the number of public trails authored on this instance only (federated trails excluded)