/** * Redis key builders for the VIN resolve cache, namespaced by decode-chain version. * * Bump DECODE_CHAIN_VERSION whenever a change to the decode chain (sources, * routing, dispatch, gating, brand mapping) could turn a previously-cached * "unknown" into a hit. Bumping instantly invalidates every stale positive and * negative entry on deploy: without it, the negative cache keeps serving the old * miss for its full TTL and a real fix looks like it "didn't work" until the * cache ages out. This was the single biggest source of phantom-undecoded VINs * (see undecoded-vin-rca.md §2/§6 — ~17 of 124 historical misses already * decoded but were masked by a stale 6h negative cache). * * Both the decode path (VehiclesService) and the admin cache-buster * (InternalVehiclesService) MUST build keys through here so they stay in sync. */ export const DECODE_CHAIN_VERSION = "3"; export function vinResolveCacheKeys(vin: string): { cacheKey: string; negKey: string; lockKey: string; } { const v = DECODE_CHAIN_VERSION; return { cacheKey: `vin:resolve:${v}:${vin}`, negKey: `vin:resolve:neg:${v}:${vin}`, // The in-flight lock is transient (60s) and version-independent — keep it // un-namespaced so concurrent decoders still dedupe across a deploy boundary. lockKey: `vin:lock:${vin}`, }; } /** * Server-side stash for a multi-candidate decode: maps the opaque candidate * keys returned to the client back to the provider-specific selection * (parts-catalogs car id / EMEX list index). Exists so the client never has to * carry decode-source details between the decode and the candidate-pick * requests. */ export function vinCandidateStashKey(vin: string): string { return `vin:candidates:${DECODE_CHAIN_VERSION}:${vin}`; }