/** * Pure scoping / digest-collapse / badge logic for the redesigned * notifications pane (A4, notifications-pane redesign). Daemon-agnostic and * fully unit-testable: the daemon route resolves caller membership + the * authoritative pending-join set (which require `agent`), then hands the raw * rows + that resolved context here to produce the frozen §3 wire response. * * Wire contract (plan §3, frozen): * GET /api/notifications → { notifications: NotifWire[]; badgeCount; scopeUnknown? } * NotifWire is discriminated on `type`: * join_request | join_approved | join_rejected (id:number) * assertion_activity (id:string digestKey, collapsed) * * Rules implemented here: * - FAIL CLOSED: caller identity unresolved → empty list, badge 0, * scopeUnknown:true (client renders "Verifying access…", never "all * caught up"). * - Type allowlist: only join_request / join_approved / join_rejected / * assertion_activity reach the pane (legacy noise + stray types dropped). * - Membership scope: every row must be in the caller's member-CG set. * - join_request narrowing (curated-only) + reconcile (G3): a join_request * survives only if its CG is in the curated set AND its (cg, agent) is * still in the authoritative pending set. Also dedups to one row per * (cg, agent), keeping the newest. * - Activity digest collapse: assertion_activity rows group by * (contextGraphId, kind, windowBucket). The caller's OWN activity is * INCLUDED (operators want visibility into their own agents) — a sole-self * digest is flagged `bySelf` so the client renders a "You" indicator. * `soleAuthor` + `actorAgentDid` are set only when exactly one OTHER * author dominates the digest. * - badgeCount = unread join_request + join_approved + assertion_activity * digests; EXCLUDES join_rejected. */ import { type NotificationRow, type AssertionActivityKind } from './db.js'; /** B8 confirmed-discount alert — server-confirmed, wallet-scoped (the * publishing wallet's business, not the whole CG's; cf. invariant 3). */ export declare const PCA_COST_COVERED_TYPE = "pca_cost_covered"; export type NotifWire = { type: 'join_request'; id: number; ts: number; read: 0 | 1; contextGraphId: string; meta: { contextGraphName?: string; agentAddress: string; agentName?: string; }; } | { type: 'join_approved'; id: number; ts: number; read: 0 | 1; contextGraphId: string; meta: { contextGraphName?: string; agentAddress: string; }; } | { type: 'join_rejected'; id: number; ts: number; read: 0 | 1; contextGraphId: string; meta: { contextGraphName?: string; agentAddress: string; }; } | { type: 'assertion_activity'; id: string; ts: number; read: 0 | 1; contextGraphId: string; meta: { contextGraphName?: string; kind: AssertionActivityKind; count: number; actorAgentDid?: string; actorAgentName?: string; soleAuthor?: boolean; /** True when the sole author is the reading agent → render "You". */ bySelf?: boolean; }; } | { type: 'pca_cost_covered'; id: number; ts: number; read: 0 | 1; contextGraphId: string; meta: { contextGraphName?: string; accountId: string; epoch: number; baseCost: string; discountedCost: string; drawnFromEpoch: string; drawnFromTopUp: string; publisherAddress: string; }; }; export interface ScopedNotificationsResult { notifications: NotifWire[]; badgeCount: number; scopeUnknown?: boolean; } export interface NotificationScopeContext { /** False when the request token did not resolve to an agent → fail closed. */ callerResolved: boolean; /** CGs the caller is involved in (curator OR allowlisted participant). */ memberCgIds: Set; /** Subset of member CGs the caller curates (gates incoming join requests). */ curatedCgIds: Set; /** * Authoritative pending join set per curated CG: cg → set of lowercased * requester agent addresses still `status==='pending'`. A join_request row * not present here was resolved (approved/denied) and is dropped (G3). */ pendingByGraph: Map>; /** The reading agent's DID (`did:dkg:agent:…`) for activity self-suppression. */ selfAgentDid?: string; /** Optional cgId → display name resolution. */ contextGraphNames?: Map; /** Optional agentDid → display name resolution (activity sole-author chip). */ agentNames?: Map; } /** * Collapse raw rows into the scoped, typed, digest-collapsed wire response. * Pure: no I/O. The caller supplies already-resolved membership + pending * context (see `NotificationScopeContext`). */ export declare function scopeNotifications(rows: NotificationRow[], ctx: NotificationScopeContext): ScopedNotificationsResult; //# sourceMappingURL=notifications-scope.d.ts.map