From 55af76f8b43e10202f15b244eeed2d1430e6c8e4 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:10:32 +0200 Subject: [PATCH] feat(shared): types for outbound peering gate (intent, subscriptions, notifications) --- packages/shared/src/types.ts | 97 ++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 678c9121..9cf6af5a 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -467,6 +467,8 @@ export type ServerEvent = | { type: 'federation_peer_active'; peerOrigin: string } | { type: 'federation_peers_changed' } | { type: 'federation_approval_request_received'; origin: string; instanceName?: string } + | { type: 'peering_subscription_changed' } + | { type: 'peering_notification_received'; kind: PeeringNotificationKind } | { type: 'dm_owner_updated'; dmChannelId: string; newOwnerId: string } | { type: 'pong' } | { type: 'error'; message: string }; @@ -1026,3 +1028,98 @@ export interface FederationPeer { rotationInProgress: boolean; createdAt: number; } + +// ─── Outbound peering gate ────────────────────────────────────────────────── + +/** + * Why a user-initiated federation action triggered the outbound peering gate. + * Recorded on `peer_approval_subscribers.trigger_reason` so admins can see + * the human-readable cause and the user can recover their original action + * after approval. Persisted as a string column with this exact set of values. + */ +export type TriggerReason = 'friend_add' | 'space_join' | 'direct_message'; + +/** + * Caller intent passed into `ensurePeered()`. The gate (when + * `autoAcceptPeering=0` and no peer row exists) branches on `kind`: + * - 'user_action': queue an outbound approval request and surface + * `admin_required` to the caller so the user sees a clear pending state. + * - 'system': skip queueing; surface `admin_required` so the calling + * subsystem (e.g. background relay) can fail loudly without spamming + * admin queues with rows nobody asked for. + * + * `target` is the human-readable target identifier the user acted on + * (e.g. `username@instance.example` for friend_add, the space invite code + * for space_join, the federated DM channel id for direct_message). + */ +export type EnsurePeeredCallerIntent = + | { kind: 'user_action'; userId: string; reason: TriggerReason; target: string } + | { kind: 'system' }; + +/** + * Terminal-state notification kinds delivered to subscribers when the + * outbound queue resolves. 'expired' is delivered by the storage janitor + * before it deletes an unresolved outbound queue row past `expiresAt`. + */ +export type PeeringNotificationKind = 'approved' | 'denied' | 'expired'; + +/** + * Per-user pending row joined from `peer_approval_subscribers` to its parent + * `peer_approval_requests`. Returned from + * `GET /api/federation/peering-subscriptions`. Used to render the user's own + * "waiting on admin" surface so they remember which actions are blocked. + */ +export interface PeeringSubscription { + id: string; + requestId: string; + peerOrigin: string; + peerInstanceName: string | null; + triggerReason: TriggerReason; + triggerTarget: string; + createdAt: number; +} + +/** + * Terminal-state notification row returned from + * `GET /api/federation/peering-notifications`. Persists until the user + * explicitly reads (sets `readAt`) or the janitor cleans up read rows + * older than the retention window. + */ +export interface PeeringNotification { + id: string; + kind: PeeringNotificationKind; + peerOrigin: string; + triggerReason: TriggerReason; + triggerTarget: string; + createdAt: number; + readAt: number | null; +} + +/** + * Subscriber summary embedded in the admin-facing approval request response + * for outbound rows. Lets the admin see which users are waiting on each + * outbound request without a separate fetch. + */ +export interface ApprovalRequestSubscriberSummary { + userId: string; + username: string; + triggerReason: TriggerReason; + triggerTarget: string; +} + +/** + * Admin-facing approval request row returned from + * `GET /api/federation/approval-requests`. Inbound rows are remote + * instances asking to peer with us; outbound rows are local users asking + * us to peer with a remote instance. Outbound rows include `subscribers` + * so the admin can see who is waiting. + */ +export interface ApprovalRequest { + id: string; + direction: 'inbound' | 'outbound'; + origin: string; + instanceName: string | null; + requestedAt: number; + expiresAt: number; + subscribers?: ApprovalRequestSubscriberSummary[]; +}