Skip to main content

Progressive Home — DA/GAQ → Fastlane Troubleshooting

Self-contained debug reference for the landing flow that takes a Mulesoft-issued Progressive quote from DA/GAQ, looks it up in the CRN legacy database, and duplicates it inside the goosehead Progressive agent portal. Drop this file into a chat alongside the carrier rules and this agent has full context to help debug.

Companion docs: entry-from-da.md for the architecture, troubleshooting-config-driven.md for the broader config-driven step flow (including Progressive QA instability in issue #5).

TL;DR — What the Flow Does

Landing URL:

FASTLANE_URL?sessionId=<uuid>&carrierId=377&lob=Home&state=TX&q=Q84275244&config-driven=true

On arrival, the frontend bootstrap hook calls POST /api/v1/progressive/home/initial-quote which:

  1. Short-circuits if carrier_quote_sessions already has a duplicate for (sessionId, daQuoteNumber).
  2. Queries CRN public.sf_quote_response for the Progressive Home quote by company_quote_number__c.
  3. Parses the ACORD HomePolicyQuoteInqRq XML into CrnProgressiveHomeQuoteData.
  4. Caches the snapshot in external_quote_responses (provider='CRN_LEGACY').
  5. Logs into Progressive under our agent credentials and opens a new session (RouteQuote?app=NewQuote).
  6. Submits NamedInsured mapped from CRN.
  7. Advances to ProductsHO via GET CurrentWorkflowState?workflowNode=ProductsHO. Progressive mints the duplicate Q-number as part of this advance — we extract it from the HAL Embedded / Extenders (any of AsiQuoteNumber, CompanyQuoteNumber, CompanysQuoteNumber, QuoteNumber).
  8. Caches the live Progressive session in ProgressiveApiSessionCache under the fresh syncId so subsequent step-config calls from the frontend reuse the same HTTP session (cookies + workflow cursor) instead of falling back to the origin DA Q-number.
  9. Returns workflowNode: 'NamedInsured' in the response. The Progressive workflow cursor is on ProductsHO at this point, but users should land on NamedInsured so they can review the CRN-mapped applicant data before stepping forward. When the frontend then calls POST /progressive/home/step-config with workflowNode=NamedInsured, the step service issues a GoToWorkflowState to navigate back — this is the same mechanism used for any inter-step navigation in the config-driven flow.
  10. Persists a row in carrier_quote_sessions with status='active', metadata.syncId, metadata.workflowNode='NamedInsured', metadata.currentPage (the ProductsHO HAL page from when the Q-number was minted), and carrierQuoteNumber set to the newly-minted duplicate Q-number (or NULL if Progressive did not return one — rare edge).
  11. If NamedInsured is rejected or the workflow cursor never reaches ProductsHO, the handler throws DuplicateQuoteIncompleteError, persists a status='error' audit row (with metadata.failureReason + metadata.failedFieldEdits), and the controller returns success: false.

Frontend on success: true: sets syncId, workflowNode, dup-bootstrapped=true; overwrites q with duplicateQuoteNumber only when it's non-null (i.e. Progressive minted one). While q is still the origin DA number, downstream step-config calls MUST carry the fresh syncId so the cached session is hit — not the origin Q-number. On success: false the URL is left alone so the operator can retry or inspect.

URL / Query Param Matrix

ParamRequiredNotes
sessionIdyesFastlane session UUID. Must exist so GET /session/:sessionId/initialize succeeds (Redis → CRN fallback).
carrierIdyes377 resolves to Progressive Home via ghcms-carrier-mappings.seed.ts.
lobyesHome.
statetechnically optionalIf present, overrides CRN's property state when calling Progressive. If omitted, the handler falls back to crnData.propertyAddress.state. Wrong state → Progressive rejects eligibility.
qyesDA-issued Q-number. Bootstrap only fires when q matches /^Q\d{6,12}$/. A freshly-duplicated Q-number (same regex shape) will also match — we rely on dup-bootstrapped=true to prevent re-fire.
config-drivenyesMust be true. Relaxed gate: no longer requires mock=true or DEV mode.
syncId, dup-bootstrappedno (written by bootstrap)Either one present causes the bootstrap hook to skip. workflowNode alone does not suppress the bootstrap.

Environment Prereqs

  • API gateway (fastlane-api-gateway) running.
  • Fastlane portal (fastlane-portal) running.
  • Local Postgres container goosehead-postgres up (Docker).
  • .env contains valid API_GATEWAY_PROGRESSIVE_USER_ID / API_GATEWAY_PROGRESSIVE_USER_PASSWORD / API_GATEWAY_PROGRESSIVE_PGR_AGENT_CODE / API_GATEWAY_PROGRESSIVE_ASI_AGENT_CODE.
  • Progressive QA reachable. See troubleshooting-config-driven.md issue #5 — QA is routinely unstable; not a VPN / local-network problem.
  • Local Postgres has the origin_carrier_quote_number column on fastlane.carrier_quote_sessions (applied by 20260420160000_add_progressive_origin_quote_tracking).

Component Map (workspace-relative paths)

LayerPath
CRN lookuplibs/apis/crn-legacy-db/src/lib/services/crn-progressive-home-lookup.service.ts
ACORD parserlibs/apis/crn-legacy-db/src/lib/utils/progressive-home-acord-parser.ts
CRN domain typeslibs/apis/crn-legacy-db/src/lib/types/crn-progressive-home.types.ts
CRN → domain mapperlibs/apis/carriers/progressive/src/lib/application/services/crn-progressive-home-request.mapper.ts
Duplicator orchestratorlibs/apis/carriers/progressive/src/lib/application/services/progressive-quote-duplicator.service.ts
Gateway handlerapps/apis/fastlane-api-gateway/src/app/handlers/progressive-home-initial-quote.handler.ts
Gateway route (POST /api/v1/progressive/home/initial-quote)apps/apis/fastlane-api-gateway/src/app/controllers/progressive.controller.ts
DTOlibs/apis/carriers/progressive/src/lib/application/dtos/initial-quote.dto.ts
Frontend bootstrap hookapps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/use-initial-quote-bootstrap.ts
Frontend API clientapps/fastlane-portal/src/app/pages/carriers/progressive/home/services/progressive-initial-quote-api.ts
Config-driven gateapps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.ts
Config-driven contextapps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/config-driven-flow-context.tsx
Prisma schema (CarrierQuoteSession.originCarrierQuoteNumber)prisma/schema.prisma
Migrationprisma/migrations/20260420160000_add_progressive_origin_quote_tracking/migration.sql

Expected Backend Log Pattern (Happy Path)

Tail the terminal running fastlane-api-gateway:serve. First-time duplication takes roughly 60–180s (Progressive login is ~40–60s, plus session factory, NamedInsured submit, two GET CurrentWorkflowState calls, eligibility PUT, flow-type PUT). Exact log class tags (shown in brackets) come from new Logger(ClassName.name) in each service.

Key lines to grep for, in order:

[ProgressiveHomeInitialQuoteHandler] initial-quote: sessionId=... daQuote=Q84275244
[CrnProgressiveHomeLookupService] Looking up Progressive Home quote in CRN: Q84275244
[ProgressiveQuoteDuplicatorService] Duplicating Progressive Home quote from CRN: origin=Q84275244 sessionId=... state=TX applicant=Alice Wonderland
[ProgressiveQuoteDuplicatorService] Auth session acquired for duplication flow
[ProgressiveApiSessionFactory] Session created: syncId=<uuid>, sessionIndex=0, state=TX, product=HO
[ProgressiveQuoteDuplicatorService] New Progressive session created: syncId=<uuid>
[ProgressiveQuoteDuplicatorService] Submitting NamedInsured for duplicate of Q84275244
[ProgressiveApiLogger] [step:NamedInsured] POST <redacted-url>/NextWorkflowState?... -> 201 (<ms>)
[ProgressiveQuoteDuplicatorService] [NamedInsured] currentPage=..., nextLink=..., hasErrors=false
[ProgressiveApiLogger] [current:ProductsHO] GET <redacted-url>/CurrentWorkflowState?workflowNode=ProductsHO -> 200 (<ms>)
[ProgressiveQuoteDuplicatorService] [ProductsHO:initial] currentPage=..., hasErrors=false
[ProgressiveApiLogger] [current:ProductsHO] GET <redacted-url>/CurrentWorkflowState?workflowNode=ProductsHO -> 200 (<ms>)
[ProgressiveQuoteDuplicatorService] [ProductsHO:initial] currentPage=Workflow/.../ProductsHO, hasErrors=true, activeViewModelEdits=true, fieldEdits=N
[ProgressiveQuoteDuplicatorService] Cached Progressive session under syncId=<uuid>
[ProgressiveQuoteDuplicatorService] Duplicate created: origin=Q84275244 duplicate=<Q-number> syncId=<uuid>

Then the frontend calls step-config with workflowNode=NamedInsured (because that's what the backend returned in the duplicate response), which triggers the live step service to navigate the Progressive cursor back:

[ProgressiveController] step-config: quote=<new-Q> syncId=<uuid> state=TX product=HO3 node=NamedInsured
[ProgressiveStepService] goToStep: syncId=<uuid> node=NamedInsured
[ProgressiveApiLogger] [goto:ProductsHO->Applicants] POST GoToWorkflowState?workflowNode=ProductsHO&progressBarItemId=Applicants -> 201

The hasErrors=true, fieldEdits=N on the ProductsHO state is normal — those are the ProductsHO fields the user will fill after stepping forward from NamedInsured via the config-driven UI.

If NamedInsured is rejected with field-level edits (HTTP 400) — for example a malformed phone or DOB — the flow fails loudly instead of marching into cascade 500s:

[step:NamedInsured] POST NextWorkflowState -> 400
[ProgressiveHttpApiClient] [step:NamedInsured] Progressive returned 400 with 1 field edit(s): PhoneNumber:The entered value exceeds defined length.

If NamedInsured succeeded but the workflow cursor never reached ProductsHO, the duplicator throws DuplicateQuoteIncompleteError:

[ProgressiveHomeInitialQuoteHandler] initial-quote failed: Progressive duplicate flow did not produce a Q-number (origin=Q84275244 syncId=<uuid> stalledAt=NamedInsured page=...): PhoneNumber:The entered value exceeds defined length.

In both cases the handler persists a carrier_quote_sessions row with status='error', carrierQuoteNumber IS NULL, and metadata.failureReason / metadata.failedFieldEdits populated for postmortem. Note: simply landing on ProductsHO with field edits is NOT a failure — those edits are the user-facing questions the config-driven UI will prompt for.

Second call with the same (sessionId, daQuoteNumber) short-circuits — no CRN, no Progressive:

[ProgressiveHomeInitialQuoteHandler] initial-quote: sessionId=... daQuote=Q84275244
[ProgressiveHomeInitialQuoteHandler] Reusing existing duplicate: origin=Q84275244 duplicate=Q90001234 syncId=<uuid>

Smoke Tests

1. Curl the endpoint directly

# Default gateway port is 3000 (overridable via GOOSEHEAD_API_GATEWAY_PORT or PORT).
curl -sS -X POST http://localhost:3000/api/v1/progressive/home/initial-quote \
-H 'Content-Type: application/json' \
-d '{"sessionId":"00000000-0000-0000-0000-000000000001","daQuoteNumber":"Q84275244","state":"TX","productCode":"HO3"}' \
| jq .

Expected on first call:

{
"success": true,
"data": {
"sessionId": "00000000-0000-0000-0000-000000000001",
"originCarrierQuoteNumber": "Q84275244",
"duplicateQuoteNumber": "Q90001234",
"syncId": "...",
"workflowNode": "ProductsHO",
"reused": false
}
}

Second call returns the same payload with "reused": true.

2. End-to-end browser

http://localhost:<portal-port>/?sessionId=<real-session-uuid>&carrierId=377&lob=Home&state=TX&q=Q84275244&config-driven=true

Watch for:

  1. Loading state for ~60–180s while bootstrap runs.
  2. URL rewrites to include &syncId=...&workflowNode=ProductsHO&dup-bootstrapped=true. q becomes the new duplicate Q-number if Progressive issued one; otherwise q stays as the DA Q-number but dup-bootstrapped=true prevents re-firing.
  3. Config-driven Products page renders.
  4. DevTools Network: exactly one POST /progressive/home/initial-quote, followed by POST /progressive/home/step-config.

3. Verify persistence

-- Mapping from DA Q-number to our duplicate
SELECT "quoteId",
"originCarrierQuoteNumber" AS origin_q,
"carrierQuoteNumber" AS duplicate_q,
status,
"lastApiCall"
FROM fastlane.carrier_quote_sessions
WHERE carrier = 'PROGRESSIVE' AND lob = 'HOME'
ORDER BY "lastApiCall" DESC
LIMIT 5;

-- CRN snapshot cache
SELECT "quoteId",
"carrierQuoteNumber" AS origin_q,
status,
"createdAt"
FROM fastlane.external_quote_responses
WHERE carrier = 'PROGRESSIVE' AND lob = 'HOME' AND provider = 'CRN_LEGACY'
ORDER BY "createdAt" DESC
LIMIT 5;

Failure Modes

A. Backend returns success: false

error stringMeaningAction
Originating Progressive quote not found in CRN legacy database.No row in sf_quote_response for that Q-number + carrier='Progressive' + line_of_business__c='Home'.Confirm the DA Q-number is correct and the Mulesoft callback finished writing. Query CRN directly (see below).
CRN quote data is malformed and could not be parsed.ACORD XML is missing HomePolicyQuoteInqRq or applicant/property sections.Inspect quote_request_xml__c in CRN for that Q-number.
Could not authenticate to Progressive agent portal. Try again in a moment.Progressive login failed. Usually QA instability (see troubleshooting-config-driven #5), MFA timing out, or stale creds.Retry. If persistent, check PROGRESSIVE_* env vars and the MFA dedicated inbox.
Progressive rejected step:NamedInsured (HTTP 400) with N field edit(s): <prop>:<description> (thrown as ProgressiveFieldValidationError)The NamedInsured payload violated a field contract. Most common: phone > 10 digits or DOB not in YYYYMMDD.Check metadata.failedFieldEdits on the persisted carrier_quote_sessions row. If PhoneNumber is flagged, inspect raw CRN phone (leading "1", extensions). If DateOfBirth is flagged, confirm the ACORD parser is yielding an ISO date; ProgressiveDate will normalize it to YYYYMMDD.
Progressive duplicate flow did not produce a Q-number (origin=... syncId=... stalledAt=...) (thrown as DuplicateQuoteIncompleteError)NamedInsured + eligibility + flow-type all returned 2xx but ProductsHO state had no AsiQuoteNumber/CompanyQuoteNumber.Read logs/progressive/ for that syncId — search for any field edits in the final ProductsHO HAL. Run the same inputs manually in FAO; Progressive usually needs one more field resolved before it mints a Q-number.
Failed to duplicate Progressive quote from CRN. Please try again.Fallthrough; Progressive rejected a step we don't yet classify.Check API gateway logs for the first 4xx/5xx after the Duplicating Progressive Home quote line.

B. DTO validation 400

InitialQuoteDto enforces:

  • sessionId must be a UUID.
  • daQuoteNumber must match /^Q\d{6,12}$/.

If the frontend sends a malformed value you'll see a NestJS validation error before the handler runs.

C. Frontend never bootstraps

Symptoms: URL stays on DA Q-number, no /initial-quote call in Network, the StandardFlow renders instead of the config-driven shell, gateway logs show session init calls but no /progressive/home/* traffic.

Causes to check (in order):

  1. Root redirect dropping query params. The app's root route in apps/fastlane-portal/src/app/app.tsx uses <Navigate to={defaultRoute} replace /> to redirect / to /carriers/<carrier>/<lob>/. If that Navigate doesn't carry location.search, everything except params hydrated into Zustand (sessionId, lob, carrierId, state) gets stripped — including q and config-driven=true. The fix is to={`${defaultRoute}${location.search}`}. Verify by landing on /?q=...&config-driven=true and checking the resulting URL still has those params after the redirect.
  2. config-driven=true missing or typo'd. The gate is in apps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.ts. Any other value short-circuits the entire config-driven shell.
  3. sessionId missing / Zustand sessionUuid never populated. The bootstrap hook waits for sessionUuid before calling. Check useSessionInit logs.
  4. URL already has dup-bootstrapped=true or syncId — the hook intentionally skips. Strip those from the URL and reload.
  5. q doesn't match ^Q\d{6,12}$. The guard in use-initial-quote-bootstrap.ts treats non-legacy patterns (including a freshly-duplicated Q) as already-bootstrapped.

D. Progressive says eligibility failed

Log pattern:

[eligibility:PropertyAddress] PUT -> 400

Usually one of:

  • state URL param disagrees with the CRN property address state.
  • CRN data was thin and CrnProgressiveHomeRequestMapper fell back to defaults that don't match (year built, sqft, foundation, exterior walls). Inspect ValidationDetails on the HAL response to see which field Progressive complained about.
  • Agent code / product combo is not licensed for that state in our test creds.

Fix: either correct the landing URL's state, or extend the mapper / CRN parser if CRN is providing richer data that isn't being surfaced.

E. Progressive 500 / bare HAL empty body

[step:NamedInsured] POST NextWorkflowState -> 500

Almost always Progressive QA instability — see troubleshooting-config-driven #5. Retry.

If it's deterministic across retries, compare the NamedInsured payload the duplicator built against a live FAO capture (Playwright MCP) — the mapper might be dropping a field Progressive now requires.

F-pre. Session cache hand-off from duplicator to step-config

The contract: ProgressiveQuoteDuplicatorService calls ProgressiveApiSessionCache.store(session.syncId, session) before returning. Subsequent POST /progressive/home/step-config calls hit ProgressiveSessionResolver.resolveBySyncId(syncId, fallbackQuoteNumber) — on cache hit, the live cookie jar is reused (no re-login). On cache miss, the resolver falls back to fallbackQuoteNumber, which for DA landings is the origin DA Q-number. That fallback opens the origin quote in-place, violating isolation.

Verify cache hand-off: After /initial-quote returns, the backend logs should show:

[ProgressiveQuoteDuplicatorService] Cached Progressive session under syncId=<uuid>

And the first follow-up /step-config should not emit SyncId cache miss, falling back to quoteNumber=Q.... If it does, inspect:

  1. Is the frontend passing syncId in the step-config request body? Check apps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.ts and the step-config API call.
  2. Is the cache TTL (SESSION_TTL_MS = 30 * 60 * 1000) expired? Fresh syncs should not hit this, but very slow UI flows can.
  3. Is Redis down? Memory fallback should still serve within the same process.

F. Duplicate did not reach ProductsHO (fails hard)

Context: The duplicator succeeds as long as the Progressive workflow cursor lands on ProductsHO — a Q-number is NOT required at that point, Progressive mints it later when the user submits ProductsHO from the config-driven UI. Failure only means the cursor is stuck before ProductsHO (typically still on NamedInsured due to malformed payload).

Current behavior: On failure the duplicator throws DuplicateQuoteIncompleteError and the handler persists a status='error' audit row with:

  • carrierQuoteNumber = NULL
  • metadata.syncId — the Progressive session that stalled
  • metadata.workflowNode — last known workflow node (e.g. NamedInsured)
  • metadata.currentPage — the HAL CurrentPageNav
  • metadata.failureReason — stringified error message
  • metadata.failedFieldEdits — array of { property, description } extracted from the HAL's embedded questions

The HTTP response is success: false with the full error message. The frontend will not rewrite the URL, and the next call with the same (sessionId, daQuoteNumber) will re-run the duplication from scratch (since findExistingDuplicate requires status='active' AND a cached metadata.syncId).

Historical note (pre-2026-04-20 fixes): An earlier design auto-submitted ProductsHO to try to mint a Q-number before handing off to the UI. That was wrong — ProductsHO requires user-facing fields (HomeClosingRiskFlag, ResidenceType, HomeForeclosureFlag, VerifyNoInelCond, VerifyNoUwCond, PolicyEffectiveDate) that aren't in the CRN snapshot. The fix: land on ProductsHO, cache the session by syncId, and return.

Debug checklist:

  1. SELECT metadata FROM fastlane.carrier_quote_sessions WHERE "quoteId"='<sessionId>' AND carrier='PROGRESSIVE' AND lob='HOME'; — read failedFieldEdits.
  2. Tail logs/progressive/ for that syncId.
  3. Search the file for HasEdit":true — the questions next to it are the exact fields Progressive rejected.
  4. If PhoneNumber is flagged: check applicant.phone from the CRN ACORD parse. UsPhoneNumber.fromRaw strips a leading "1" but throws on anything else that can't be normalized to 10 digits.
  5. If DateOfBirth is flagged: the ProgressiveDate VO expects YYYY-MM-DD or YYYYMMDD; anything else throws before the HTTP call even goes out.
  6. If the last known page is NamedInsured but no field edits were captured, NamedInsured was accepted and the eligibility/flow-type calls may have 5xx'd — those now throw, so look for a different error message instead.

G. .trim is not a function or similar type mismatch on CRN data

Symptom: POST /initial-quote returns success: false with an error like (value ?? "").trim is not a function. Logs show CRN lookup succeeded (Looking up Progressive Home quote in CRN: Q... followed by Duplicating Progressive Home quote from CRN: origin=Q... applicant=...), then the crash.

Root cause: The pg driver returns JavaScript Date objects for Postgres date/timestamp columns (effective_date__c, request_date__c, time_received__c). If any code downstream assumes those fields are strings and calls .trim(), .slice(), or a regex on them, it blows up. This was the case for effective_date__c originally; fixed by coercing to ISO string at the DB boundary in CrnProgressiveHomeLookupService.toIsoDateString() and hardening CrnProgressiveHomeRequestMapper.normalizeDate() to accept unknown.

Fix pattern: For any date column added to ProgressiveHomeCrnRow, either (a) coerce to string in the lookup service, or (b) update the type to string | Date and make the consumer handle both.

H. Noisy Safeco/Root quote lookups in logs

Symptom: On a Progressive Home landing you still see logs like:

🔄 Attempting to fetch Safeco quote from CRN for session: ...
🔍 Querying CRN Legacy Database for Safeco Quote
❌ Failed to retrieve Safeco quote from CRN

Root cause: This is NOT from the DA→Fastlane flow. It's SessionStoreService.getSafecoQuote() (and getRootQuote()) which run on every page load because the session API exposes /session/safeco-quote, /session/root-quote, /session/hydra-payment endpoints that the shell invokes regardless of active LOB. For Progressive Home sessions there's no auto_quote_id, so the Safeco lookup hits rater_session and errors out. The error is swallowed and the endpoint returns 304.

Impact: None on the DA→Fastlane flow. Noise only. Silencing it is a separate cleanup in SessionStoreService/SessionController (gate by LOB).

I. Idempotency doesn't kick in

Second call for same (sessionId, daQuoteNumber) does a full duplication instead of returning "reused": true.

Check progressive-home-initial-quote.handler.ts findExistingDuplicate. It requires a row where:

  • quoteId = sessionId
  • carrier = PROGRESSIVE
  • lob = HOME
  • originCarrierQuoteNumber = daQuoteNumber
  • status = active
  • metadata.syncId is a non-empty string

carrierQuoteNumber may be NULL on reuse (the user hasn't submitted ProductsHO yet), as long as a syncId is cached. If the first call failed (see failure mode F), the row will be status='error', and the second call will correctly re-run the duplication.

Raw CRN Inspection (remote legacy DB)

Use the user-postgres-legacy-crn-mcp MCP tool, or any client configured against CRN:

-- Does the DA quote exist at all?
SELECT heroku_id,
company_quote_number__c,
company_client_id__c,
line_of_business__c,
status__c,
effective_date__c,
time_received__c,
LENGTH(quote_request_xml__c) AS req_len,
LENGTH(quote_response_xml__c) AS resp_len
FROM public.sf_quote_response
WHERE carrier__c = 'Progressive'
AND company_quote_number__c = 'Q84275244'
ORDER BY time_received__c DESC
LIMIT 1;

-- Pull the ACORD request XML
SELECT quote_request_xml__c
FROM public.sf_quote_response
WHERE company_quote_number__c = 'Q84275244'
ORDER BY time_received__c DESC
LIMIT 1;

-- Sanity: how many Progressive Home rows exist at all
SELECT heroku_id, company_quote_number__c, status__c, time_received__c
FROM public.sf_quote_response
WHERE carrier__c = 'Progressive' AND line_of_business__c = 'Home'
ORDER BY time_received__c DESC
LIMIT 10;

Raw Local DB Inspection

docker exec goosehead-postgres psql -U goosehead -d goosehead -c \
"\d+ fastlane.carrier_quote_sessions"

Useful queries:

-- Everything we've persisted for a given session
SELECT *
FROM fastlane.carrier_quote_sessions
WHERE "quoteId" = '<sessionId>'
AND carrier = 'PROGRESSIVE'
AND lob = 'HOME';

-- Everything we've cached from CRN for a given session
SELECT id, provider, "carrierQuoteNumber", status, "createdAt", "updatedAt"
FROM fastlane.external_quote_responses
WHERE "quoteId" = '<sessionId>'
AND carrier = 'PROGRESSIVE'
AND lob = 'HOME';

-- Latest ACORD snapshot blob for a DA Q-number (to compare against CRN live)
SELECT "responsePayload"
FROM fastlane.external_quote_responses
WHERE provider = 'CRN_LEGACY'
AND "carrierQuoteNumber" = 'Q84275244'
ORDER BY "createdAt" DESC
LIMIT 1;

Clearing State for a Fresh Test

If you want to re-run the duplication from scratch for the same DA Q-number + sessionId:

-- Wipe the mapping so the handler takes the duplication path again
DELETE FROM fastlane.carrier_quote_sessions
WHERE "quoteId" = '<sessionId>'
AND carrier = 'PROGRESSIVE'
AND lob = 'HOME'
AND "originCarrierQuoteNumber" = 'Q84275244';

-- Optionally wipe the CRN snapshot cache (forces a fresh CRN round-trip)
DELETE FROM fastlane.external_quote_responses
WHERE "quoteId" = '<sessionId>'
AND carrier = 'PROGRESSIVE'
AND lob = 'HOME'
AND provider = 'CRN_LEGACY';

Also strip syncId, workflowNode, and dup-bootstrapped from the browser URL before reloading.

Isolation Rules

  • The Cursor IDE browser (cursor-ide-browser) is the right place to exercise code changes against the active DA-duplicated quote (the one live in the config-driven flow). This is the quote DA just landed on and Progressive just minted under our agent credentials.
  • The Playwright MCP browser (user-playwright-mcp) is exclusively for parity work against the Progressive agent portal and must use PROGRESSIVE_POC_PLAYWRIGHT_QUOTE_NUMBER. Never open either Q-number from this DA flow (origin or duplicate) in the Playwright MCP — Progressive's workflow cursor is server-side stateful, and a concurrent browser session corrupts it for the config-driven backend.
  • Progressive's agent portal is the source of truth. Our code and docs may be outdated; parity work should dump FAO behavior and bring it into our code, not the other way around.
  • PROGRESSIVE_POC_QUOTE_NUMBER is deprecated — the active config-driven quote now comes from the DA landing URL (?q=Q...) and its duplicate. See troubleshooting-config-driven.md "Quote Isolation".
  • The duplicate flow creates a fresh Progressive session per landing — no cookie reuse across DA landings.

Quick Reference — What's Idempotent vs Not

OperationIdempotent?
POST /initial-quote with same (sessionId, daQuoteNumber) and a saved status='active' row with a cached metadata.syncIdYes — returns cached duplicate (Q-number may still be null until user submits ProductsHO).
POST /initial-quote with same (sessionId, daQuoteNumber) but the row is status='error' or metadata.syncId is missingNo — will attempt duplication again (by design).
CRN snapshot fetchYes — cached via external_quote_responses upsert.
Progressive NamedInsured submitNo — always creates a new Progressive session. The handler upserts per (quoteId, carrier, lob) so a failed row is overwritten on retry.