feat: unified entity search + related-persons graph - #154
Merged
Conversation
…ers from trpc core
…registration applied via integration patch per recon)
munisp
added a commit
that referenced
this pull request
Sep 14, 2026
…gration) (#160) - WP1 (#154): entitySearchRouter import + appRouter registration - WP2 (#158): monitoringRouter import + appRouter registration - WP3 (#155): subjectPortalRouter + computeDataCompleteness imports; subjectPortal registration; removed routers.ts-local getFallbackSuggestion (now shared in server/dataCompleteness.ts); getDataCompleteness delegates to computeDataCompleteness; consentPurposeEnum gains consumer_self_check; subjectAccessTokens/subjectDisputes pgTable declarations (matches drizzle/0023_subject_portal.sql) - WP4 (#157): shareableReportsRouter + selfServiceBillingRouter imports + registrations; reportShareLinks/planSignups pgTable declarations (matches drizzle/0024_share_links_and_plan_signups.sql) - WP5 (#156): lookup.phone procedure (gatewayFetch /v1/phone/:number, validated input) Co-authored-by: bis-integration <integration@bis.local>
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.
What
WP1
feat/entity-search— unified one-box entity search + related-persons graph, closing the Intelius one-box search + related-persons graph gap.New tRPC router
entitySearchRouterinserver/entitySearch.ts:entitySearch.search(query, protectedProcedure) — single string input with automatic type detection:0) → dual NIN + BVN gateway lookup (/v1/nin/:id,/v1/bvn/:id)RCprefix → CAC gateway lookup + tenant'scorporate_screening_profiles0…/234…/+234…, all variants matched) →investigations/kyc_records/candidate_profilesphone columnsPromise.allSettled— a failing source (e.g. gateway outage) is recorded as{source, status: "error", latencyMs, error}and never sinks the other sources. Returns{ queryType, identities, investigations, kyc, sources }.audit_log, categoryapi, HMAC integrity hash, who/what/queryType/per-source status).entitySearch.getAssociates(query) — inputinvestigationRefand/orcandidateId. Assembles a related-persons graph from: beneficial owners/directors incorporate_screening_profiles(CAC payloads),informal_referenceson the subject's informal verification cases, and shared phone/address matches inkyc_records/investigations. Returns{ nodes, edges, sources }. Confidence rules:direct_documented(beneficial owner, signed guarantor) → high;declared(self-nominated referee, landlord, association…) → medium;shared_attribute(same phone/address) → low. Graph views are audit-logged too.entitySearch.searchHistory(query) — current tenant's audit-logged searches, keyset-paginated (limit/cursor).Why
Intelius-parity gap: operators currently have to know which subsystem to query per identifier type. One box now detects the identifier and fans out across all relevant sources, and the associates graph surfaces relationships (documented vs declared vs circumstantial) that are otherwise invisible.
Security / tenancy
ctx.tenantId(never client-supplied); no tenant context →FORBIDDEN; anonymous →UNAUTHORIZED.BIS_GATEWAY_URLis unconfigured.writeAuditLoginrouters.ts); the search itself is never blocked by audit.%/_; all queries parameterized. No new tables, no secrets, no.github/workflowschanges.How tested (real output)
pnpm install(pnpm 10.27), then with the registration patch below applied locally:Coverage: query-type detection matrix (14 cases), RC/phone normalization, confidence classification (guarantor→high, declared→medium, shared→low), beneficial-owner payload extraction, per-source failure isolation (gateway outage leaves DB sources
ok), tenant isolation (every SQL carries the ctx tenant;nulltenant → FORBIDDEN), NOT_FOUND for cross-tenant refs, search-history pagination.server/routers.ts(374KB, too large for MCP push; verified locally with typecheck + tests above)The branch intentionally does not modify
server/routers.ts. Apply this exact 2-line patch on main at merge/integration time:import { entitySearchRouter } from "./entitySearch";immediately after line 142import { piiKeyCustodyRouter } from "./piiKeyCustody";entitySearch: entitySearchRouter,immediately after the linekycDocumentEvidence: kycDocumentEvidenceRouter,(currently the last entry of theappRouter = router({...})object, ~line 7692)Files changed
server/entitySearch.ts(new) — router + exported pure helpers (detectQueryType,normalizeRcNumber,phoneVariants,confidenceForEvidenceKind,evidenceKindForReferenceType,extractBeneficialOwners)server/entity-search.test.ts(new) — 37 testsRisks
extractBeneficialOwnershandles the known CAC payload shapes and returns[]on anything malformed (fail-safe, tested).AppRoutertype picks upentitySearchautomatically once the patch lands.