/** * Cross-audience federated search. * * Per architecture §7, vectors are strictly per-audience — customer * embedding pools only contain customer-visible text, partner pools only * contain partner-visible text, etc. This means the most common admin AI * use case ("find products similar to *X*" where *X* is in customer- * facing language) needs to query a non-staff audience pool. * * Staff actors are authorized to query any audience pool; customer / * partner / supplier agents are pinned to their own audience by API * authorization. This helper takes a list of `search_audiences` and: * * 1. Verifies the actor is authorized for each requested audience. * 2. Issues parallel `IndexerAdapter.search` calls — one per audience. * 3. Deduplicates hits by entity id (same entity may rank in multiple * pools; keep the representative from its best per-pool rank). * 4. Uses reciprocal-rank fusion to merge per-pool orderings without * comparing provider scores produced by independent queries. * * The current adapter surface has no multi-slice method, so this helper always * fans out. A future native path must preserve the same observable ranking and * deduplication semantics. * * See `docs/architecture/catalog-architecture.md` for the design. */ import type { IndexerAdapter, SearchRequest, SearchResults } from "@voyant-travel/catalog-contracts/indexer/contract"; import type { Visibility } from "../contract.js"; /** Default number of ranked candidates fetched from each audience pool. */ export declare const DEFAULT_FEDERATED_CANDIDATE_DEPTH = 50; /** Hard bound on candidates fetched from any one audience pool. */ export declare const MAX_FEDERATED_CANDIDATE_DEPTH = 250; export interface FederatedSearchOptions { adapter: IndexerAdapter; /** * The actor making the request. The federation helper enforces: * - `customer` / `partner` / `supplier` actors → may search only * their own audience pool (no federation). * - `staff` actors → may search any combination of audience pools. */ actor: Visibility; /** The audience pools to federate across. Must be a subset of allowed pools per actor. */ searchAudiences: Visibility[]; /** The vertical (entity_module) to search. */ vertical: string; /** Locale + market for every slice. */ locale: string; market: string; /** The base search request — same shape passed to a single-slice search. */ request: SearchRequest; /** * Ranked candidates fetched per audience before fusion. Defaults to * {@link DEFAULT_FEDERATED_CANDIDATE_DEPTH}, rises to the requested output * limit when needed, and may not exceed {@link MAX_FEDERATED_CANDIDATE_DEPTH}. */ candidateDepthPerAudience?: number; } /** * Federate a search across multiple audience pools. Returns a unified * `SearchResults` with deduplicated hits ranked by a fused score. */ export declare function federateAudienceSearch(options: FederatedSearchOptions): Promise; /** * Merge ordered `SearchResults` with reciprocal-rank fusion. Provider scores * are intentionally ignored because independently executed queries do not * share a score scale. Duplicate ids accumulate one contribution per result * list. Their representative hit comes from the best rank, with earlier input * lists breaking equal-rank ties. The returned score is the larger-is-better * fused score. `total` is the number of unique fetched candidates. It is exact * only when every input result is exhausted; otherwise `totalRelation: "gte"` * marks it as a lower bound and avoids pretending bounded fusion saw all hits. */ export declare function mergeAndDedupe(perSlice: ReadonlyArray, limit?: number): SearchResults;