fix: request only enabled TIC enrichment types (unblocks the picker) (#349)
* chore: translate TIC enrichment errors into actionable hints Adds self-diagnosing log output for known TIC failure shapes: - 'Session not completed' → tenant doesn't have enrichment enabled (the BankID consent-to-enrich dialog only fires when SPAR/CompanyRoles are configured on the tenant; without it, TIC rejects enrichment requests with this misleadingly-worded error) - 'not enabled' variants → explicit tenant disable - 'too old' → >30min between auth and enrichment request No behaviour change — purely diagnostic. Saves a future developer from re-discovering that "Session not completed" means "contact support@tic.io" rather than "retry the session." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: stop requesting disabled 'SPAR' enrichment type Root cause of the 'Session not completed' failures: TIC renamed 'SPAR' to 'Address' and our tenant has Address *disabled* (only CompanyRoles is on — verified via GET /api/v1/enrichment/types). Requesting an unknown or disabled type causes TIC to reject the whole enrichment with a misleading 'Session not completed' error rather than ignoring the unknown type or returning PartiallyCompleted. - Enrichment request now asks only for ['CompanyRoles'], which is what the /select-company picker actually needs. This is enough to stop the 'Session not completed' errors and make the picker work in prod. - If Address is enabled on TIC later, add it to the array (and wire up the address pre-fill paths in WelcomeOnboarding + createCompanyFromTicRole — both already look for a `.spar` field that TIC may have renamed). - Updated the diagnostic hint for 'Session not completed' to lead with the type-mismatch cause and point at the /enrichment/types curl for verification, since that's the first thing to check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: clean up stale SPAR references in tic extension (PR review) Greptile P2 nits — all cosmetic, no runtime impact: - fetchAndStoreEnrichment JSDoc updated from "SPAR + CompanyRoles" to just CompanyRoles, with a note about where to add Address back if/when it's enabled on the tenant. - hasSpar log field removed from the 'enrichment data shape' snapshot. enrichmentData.spar was permanently undefined after the SPAR → nothing swap; logging a constantly-false field would have misled a future developer chasing missing address data. - Inline comment on the signup-path call to fetchAndStoreEnrichment now matches the login-path comment (CompanyRoles, not SPAR + CompanyRoles). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
1583e302da
commit
7cbd791c5b
@@ -30,10 +30,14 @@ import crypto from 'crypto'
|
||||
const log = createLogger('tic/bankid')
|
||||
|
||||
/**
|
||||
* Request SPAR + CompanyRoles enrichment for a completed BankID session and
|
||||
* cache the result in `extension_data` so /select-company and the onboarding
|
||||
* wizard can pre-fill from it. Non-blocking: any failure is logged and
|
||||
* swallowed — BankID auth must still succeed even if enrichment is down.
|
||||
* Request CompanyRoles enrichment for a completed BankID session and cache
|
||||
* the result in `extension_data` so /select-company can pre-fill the picker.
|
||||
* Non-blocking: any failure is logged and swallowed — BankID auth must still
|
||||
* succeed even if enrichment is down.
|
||||
*
|
||||
* Only types currently enabled on the TIC tenant are requested — see the
|
||||
* block comment inside the function. If Address (formerly SPAR) is enabled
|
||||
* later, add it here to restore address pre-fill in the manual wizard.
|
||||
*/
|
||||
async function fetchAndStoreEnrichment(
|
||||
sessionId: string,
|
||||
@@ -41,7 +45,19 @@ async function fetchAndStoreEnrichment(
|
||||
supabase: SupabaseClient,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const enrichment = await requestEnrichment(sessionId, ['SPAR', 'CompanyRoles'])
|
||||
// IMPORTANT: only request types that are actually enabled on the TIC
|
||||
// tenant. Requesting an unknown/disabled type (e.g. 'SPAR', which TIC
|
||||
// has renamed to 'Address' and which our tenant currently has off)
|
||||
// makes TIC reject the whole enrichment with
|
||||
// `error: 'Session not completed'` — a misleading error that took a
|
||||
// round of debugging to trace. Verified via GET /api/v1/enrichment/types:
|
||||
// { type: 'CompanyRoles', enabled: true } ← we want this
|
||||
// { type: 'Address', enabled: false } ← formerly SPAR, off
|
||||
//
|
||||
// If 'Address' gets enabled later, add it here (and wire up the
|
||||
// address pre-fill in WelcomeOnboarding and createCompanyFromTicRole
|
||||
// — both already look for a `.spar` field that TIC may have renamed).
|
||||
const enrichment = await requestEnrichment(sessionId, ['CompanyRoles'])
|
||||
log.info('enrichment request returned', {
|
||||
status: enrichment.status,
|
||||
requestedTypes: enrichment.requestedTypes,
|
||||
@@ -51,8 +67,7 @@ async function fetchAndStoreEnrichment(
|
||||
|
||||
// Case-insensitive status comparison: TIC has been observed returning
|
||||
// lowercase values ('completed', 'failed') in addition to the docs' canonical
|
||||
// capitalized form. Accept both fully and partially completed runs — if the
|
||||
// tenant only has SPAR enabled (not CompanyRoles) we still want the address.
|
||||
// capitalized form. Accept both fully and partially completed runs.
|
||||
const statusLower = String(enrichment.status ?? '').toLowerCase()
|
||||
const isCompleted = statusLower === 'completed' || statusLower === 'partiallycompleted'
|
||||
const usable = isCompleted && enrichment.secureUrl
|
||||
@@ -61,17 +76,37 @@ async function fetchAndStoreEnrichment(
|
||||
// so we can diagnose why a real-user enrichment comes back non-usable.
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { secureUrl: _omit, ...responseDiagnostic } = enrichment
|
||||
log.warn('enrichment not usable — inspect response for diagnostic fields', responseDiagnostic)
|
||||
|
||||
// Interpret common failure shapes into actionable hints so developers
|
||||
// don't have to re-trace this every time. TIC returns these as body
|
||||
// fields with HTTP 200, not as errors — see TIC_AUTH.md §enrichment.
|
||||
const errField = (enrichment as { error?: string }).error ?? ''
|
||||
let hint: string | undefined
|
||||
if (errField === 'Session not completed') {
|
||||
// Two distinct causes produce this identical error:
|
||||
// 1. We requested a type not enabled on the tenant (most common —
|
||||
// verify via GET /api/v1/enrichment/types)
|
||||
// 2. The BankID session genuinely never went through the
|
||||
// consent-to-enrich dialog
|
||||
hint = 'Likely cause: a requested enrichment type is not enabled on the TIC tenant. Run `curl -H "X-Api-Key: $KEY" https://id.tic.io/api/v1/enrichment/types` to verify which types have `enabled: true` and adjust the requestEnrichment call to match.'
|
||||
} else if (errField.toLowerCase().includes('not enabled')) {
|
||||
hint = 'Enrichment explicitly disabled on TIC tenant — contact support@tic.io.'
|
||||
} else if (errField.toLowerCase().includes('too old')) {
|
||||
hint = '>30 min between auth completion and enrichment call — check for slow server-side work between /bankid/complete and fetchAndStoreEnrichment.'
|
||||
}
|
||||
|
||||
log.warn('enrichment not usable', { ...responseDiagnostic, hint })
|
||||
return
|
||||
}
|
||||
|
||||
const enrichmentData = await fetchEnrichmentData(enrichment.secureUrl)
|
||||
|
||||
// Log a PII-free snapshot so we can debug the role filter in production.
|
||||
// Raw personnummer/names are deliberately omitted.
|
||||
// Raw personnummer/names are deliberately omitted. `spar`/`address` not
|
||||
// logged — we don't request those types currently (see block comment
|
||||
// on requestEnrichment above), so they'd always be absent.
|
||||
const firstRole = enrichmentData.companyRoles?.[0]
|
||||
log.info('enrichment data shape', {
|
||||
hasSpar: !!enrichmentData.spar,
|
||||
companyCount: enrichmentData.companyRoles?.length ?? 0,
|
||||
firstRoleStatuses: firstRole
|
||||
? {
|
||||
@@ -733,7 +768,7 @@ export const ticExtension: Extension = {
|
||||
)
|
||||
}
|
||||
|
||||
// Enrichment (SPAR + CompanyRoles) — pre-fills /select-company picker.
|
||||
// Enrichment (CompanyRoles) — pre-fills /select-company picker.
|
||||
await fetchAndStoreEnrichment(sessionId, userId, supabase)
|
||||
|
||||
return NextResponse.json({
|
||||
|
||||
Reference in New Issue
Block a user