feat(identity): refresh bridged handles and serve fediverse origins master
Tidepool is renaming every bridged actor to <user>.<instance host>.ap.tdpl.io and announcing it with #identity events. Coves trusted the event's handle for users, ignored identity events for communities, and had no way to show or look up a bridged account's fediverse address. This stores only freshly verified handles, serializes refreshes per DID, adds a sweep for rows that missed their event, and adds origin fields to profiles and views. Changes: - User #identity events ignore the event's handle: the consumer purges the DID, resolves it through the new uncached resolver (identity.NewUncachedResolver), verifies the handle and writes it. Unknown DIDs cost one indexed read. A per-DID timeout debounce (4096 entries) keeps a hanging handle domain from stalling the user lane. - The community consumer handles #identity events for indexed communities through the same RefreshIdentityHandle path, checks the new handle with communities.ValidateHandle and the stored hostedBy domain, and writes it with a new Repository.UpdateHandle. Failures leave the row unchanged. - Refreshes of one DID are serialized by a session-scoped Postgres advisory lock on a dedicated pool (8 connections, separate from the application pool), held from the DID purge until the handle write. The stored handle is re-read inside the lock. A busy lock (25s lock_timeout) returns ErrIdentityRefreshBusy as a deferred redrive; a missing lock fails closed. - New cmd/reconcile-handles: selects user and community rows on trusted bridge hosts, optionally clears identity_cache, refreshes each DID under the shared lock, and reports still-differing rows with reason codes. -dry-run is read-only. Exit status is non-zero while any row differs. Runbook in docs/PRODUCTION_DEPLOY.md. - actor.profile and profileViewDetailed gain origin, originUsername, originUrl and originSoftware; community.profile, communityView and communityViewDetailed gain originUrl and originSoftware. They are stored only after a fresh, document-first verification on a trusted bridge PDS with the handle under the bridge's registrable domain, and are sanitized (UTF-8 byte limits, origin pair set together, https originUrl on the same host, originSoftware lowercased). Migrations users_profile_origin and communities_origin_display add the columns. - Post and comment authorView gains origin, originUsername and originSoftware through postViewSelectColumns/scanPostView and comment author hydration. actor.defs#profileView gains all four fields, used for notification actors and recentUpvoters. Native users and deleted-comment placeholders carry none of the keys. - The actor parameter of actor.getProfile, getPosts and getComments accepts name@host and @name@host. GetDIDByOriginUsername matches exactly, then by a unique case-insensitive match; more than one match is 409 AmbiguousActor. The scoped form only reads the database. Migration users_origin_username_index adds the lookup indexes. - docs/BRIDGED_IDENTITY.md describes the design, rollout and known open items; SECURITY_MODEL.md, TEST_ARCHITECTURE.md and LEXICON_PUBLISHING.md are updated. Deploy and lexicon notes: Coves must deploy before Tidepool renames anything and before Tidepool writes origin fields. Tidepool must re-sync its vendored lexicons from Coves main before its branch merges. None of the lexicon changes are published; run goat lex publish --update per docs/LEXICON_PUBLISHING.md. The actor parameter change (format at-identifier dropped for a plain string, maxLength 2048) is a recorded input-loosening exception. After Tidepool's rename job, run reconcile-handles with -clear-identity-cache and re-run until it exits 0. The trusted-bridge origin admission path is covered by integration tests but has not been exercised end to end against Tidepool. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>