Bust every cached page — consumer or admin — that a write touching
customerIds could have affected.
The collection tag is always cleared alongside, because an unscoped admin page carries only that tag and no customer id can reach it.
keys deletes exact entries in addition to the tag surgery, passing the
key arguments — this cache hashes and prefixes them. It exists because
invalidateTag resolves keys through the FT index, which does not exist in
memory mode (valkeyClient: null); there the tag bust is a total no-op, and
an exact-key delete is the only thing that keeps read-after-write honest.
Services that folded get onto search pass that get's arguments.
These deletes only reach this instance's own key space, because the key space is partitioned per surface. That is sufficient, and a writer must not try to name the other surface's key:
get entry is itself tagged (by customer
id, or by the collection tag when unscoped), so the tag bust above already
evicts it from the shared L2 and publishes the peer eviction.ProtectedsearchTags to write on a page, derived from the scope the query searched — never from the customers present in the result.
That distinction is load-bearing. An entry tagged with the customer ids in
the page carries no tags when the page is empty, so nothing can ever bust
it and a later create leaves it stale for the full L2 TTL. Tagging by
scope means an empty page still carries the tag for what it searched.
An unscoped read falls back to the collection tag, since no customer-id tag can reach a row whose owner did not exist when the entry was written.
StaticdeserializeThe inverse of serializeIdentity: rehydrates the Date fields.
OptionalamlVerification?: { id: string; status: "REVIEW" | "PENDING" | "APPROVED" | "DECLINED" }Anti-money laundering verification
Retrieve the other details directly from AiPrise
What this identity is capable of executing currently
Country of jurisdiction; alias of countryOfResidence
OptionalcustomerVerification?: { id: string }Details of customer's own verification of the identity
OptionaldateOfBirth?: stringIn ISO format: yyyy-MM-dd
OptionaldeletedAt?: DateOptionalidentityDocumentVerification?: { id: string; status: "REVIEW" | "PENDING" | "APPROVED" | "DECLINED" }Identity documents (passport, gov ID, etc.) verification
Retrieve the other details directly from AiPrise
OptionalreferenceId?: stringOptionaltaxId?: stringThe person's own tax identification number — an SSN or ITIN for a US person, the local equivalent elsewhere. Stored as entered.
Mirrors related_person.data.taxId: the two are the same fact about
the same kind of party, and both forward to AiPrise the same way
(additional_info: TAX_IDENTIFICATION_NUMBER on the identity-document
verification run — AiPrise's user profile has no field for it).
Discriminator shared with Organization for mixed entity lists
OptionalverificationProfileId?: stringAiPrise user profile ID
Only set if we verify the identity.
StaticgetThe exact cache key IdentityQueryService.get reads through.
get folds onto search, so its entry is an ordinary single-row search
page — these args must stay identical to the ones get passes
({ customerId, ids: [id], pageSize: 1 }, with search's page default of
1). Every other search filter is undefined there, and serviceCacheKey
canonicalizes with JCS, which drops undefined properties — so naming only
the four that are set produces the same digest. identityCache.test.ts
pins that equivalence.
Writers pass this to SearchCache.invalidateCache's keys so
read-after-write holds in memory mode, where tag invalidation no-ops. The
cache hashes and surface-prefixes it; this returns the arguments only.
StaticserializeIdentity → its JSON-safe form. Safe to call without a cache instance.
OptionalamlVerification?: { id: string; status: "REVIEW" | "PENDING" | "APPROVED" | "DECLINED" }Anti-money laundering verification
Retrieve the other details directly from AiPrise
What this identity is capable of executing currently
Country of jurisdiction; alias of countryOfResidence
OptionalcustomerVerification?: { id: string }Details of customer's own verification of the identity
OptionaldateOfBirth?: stringIn ISO format: yyyy-MM-dd
OptionaldeletedAt?: DateOptionalidentityDocumentVerification?: { id: string; status: "REVIEW" | "PENDING" | "APPROVED" | "DECLINED" }Identity documents (passport, gov ID, etc.) verification
Retrieve the other details directly from AiPrise
OptionalreferenceId?: stringOptionaltaxId?: stringThe person's own tax identification number — an SSN or ITIN for a US person, the local equivalent elsewhere. Stored as entered.
Mirrors related_person.data.taxId: the two are the same fact about
the same kind of party, and both forward to AiPrise the same way
(additional_info: TAX_IDENTIFICATION_NUMBER on the identity-document
verification run — AiPrise's user profile has no field for it).
Discriminator shared with Organization for mixed entity lists
OptionalverificationProfileId?: stringAiPrise user profile ID
Only set if we verify the identity.
Root of the identity class hierarchy: it owns the one
identity-searchcache every identity surface shares, the single seam that busts it, and the serialization that defines the cached shape.Every reader and writer of
identityextends this class —IdentityQueryService(consumer read),IdentityService(consumer writer),IdentityAdminQueryService(admin read) andIdentityAdminService(admin writer, whose AiPrise verification callbacks mutate the table outsideIdentityServiceentirely). A writer that forgets to bust is then a visible omission in one place rather than a silently stale cache, and a class that both reads and writes busts the instance it reads through — which is what keeps read-after-write correct inside a single request.serializeIdentity / deserializeIdentity are static so that Next.js can call them without a Valkey client: the dashboards need exactly this projection to cross the RSC → client boundary, and before this class existed each of them carried its own copy to drift against.