What happens
lib/compliance/dpdp.ts checkConsent (~L206) queries purposeCodes: { has: purposeCode } with the canonical code (SESSION_BOOKING). lib/compliance/purpose-codes.ts documents LEGACY_PURPOSE_CODE_MAP / normalizePurposeCode() for the pre-taxonomy kebab-case codes, but the only callers are the org consent dashboard route. An artifact stored as session-booking is therefore invisible to the gate that matters, and every booking against that consultant answers 403 CONSENT_WITHDRAWN although withdrawnAt is null.
#1394 covers the seed side (81 of 83 seeded consultants carry the legacy code). This issue is the code side: a consent record is a legal artifact, and the gate must recognise every code the platform ever wrote. The withdraw path (~L242, ~L274) has the same exact-match shape.
Fix
Resolve the canonical code plus its legacy aliases once (a helper next to normalizePurposeCode, e.g. purposeCodeAliases(code)) and query purposeCodes: { hasSome: aliases } in checkConsent and the withdraw lookups. No backfill. One pin: a session-booking artifact satisfies a SESSION_BOOKING check and is withdrawn by a SESSION_BOOKING withdrawal. Fold into #1465 (B2C checkout refusals).
Found by the wave 2B enterprise run of the finance train (plan §8).
What happens
lib/compliance/dpdp.tscheckConsent(~L206) queriespurposeCodes: { has: purposeCode }with the canonical code (SESSION_BOOKING).lib/compliance/purpose-codes.tsdocumentsLEGACY_PURPOSE_CODE_MAP/normalizePurposeCode()for the pre-taxonomy kebab-case codes, but the only callers are the org consent dashboard route. An artifact stored assession-bookingis therefore invisible to the gate that matters, and every booking against that consultant answers 403CONSENT_WITHDRAWNalthoughwithdrawnAtis null.#1394 covers the seed side (81 of 83 seeded consultants carry the legacy code). This issue is the code side: a consent record is a legal artifact, and the gate must recognise every code the platform ever wrote. The withdraw path (~L242, ~L274) has the same exact-match shape.
Fix
Resolve the canonical code plus its legacy aliases once (a helper next to
normalizePurposeCode, e.g.purposeCodeAliases(code)) and querypurposeCodes: { hasSome: aliases }incheckConsentand the withdraw lookups. No backfill. One pin: asession-bookingartifact satisfies aSESSION_BOOKINGcheck and is withdrawn by aSESSION_BOOKINGwithdrawal. Fold into #1465 (B2C checkout refusals).Found by the wave 2B enterprise run of the finance train (plan §8).