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:
- Short-circuits if
carrier_quote_sessionsalready has a duplicate for(sessionId, daQuoteNumber). - Queries CRN
public.sf_quote_responsefor the Progressive Home quote bycompany_quote_number__c. - Parses the ACORD
HomePolicyQuoteInqRqXML intoCrnProgressiveHomeQuoteData. - Caches the snapshot in
external_quote_responses (provider='CRN_LEGACY'). - Logs into Progressive under our agent credentials and opens a new session (
RouteQuote?app=NewQuote). - Submits
NamedInsuredmapped from CRN. - Advances to
ProductsHOviaGET CurrentWorkflowState?workflowNode=ProductsHO. Progressive mints the duplicate Q-number as part of this advance — we extract it from the HALEmbedded/Extenders(any ofAsiQuoteNumber,CompanyQuoteNumber,CompanysQuoteNumber,QuoteNumber). - Caches the live Progressive session in
ProgressiveApiSessionCacheunder the freshsyncIdso subsequentstep-configcalls from the frontend reuse the same HTTP session (cookies + workflow cursor) instead of falling back to the origin DA Q-number. - Returns
workflowNode: 'NamedInsured'in the response. The Progressive workflow cursor is onProductsHOat this point, but users should land onNamedInsuredso they can review the CRN-mapped applicant data before stepping forward. When the frontend then callsPOST /progressive/home/step-configwithworkflowNode=NamedInsured, the step service issues aGoToWorkflowStateto navigate back — this is the same mechanism used for any inter-step navigation in the config-driven flow. - Persists a row in
carrier_quote_sessionswithstatus='active',metadata.syncId,metadata.workflowNode='NamedInsured',metadata.currentPage(theProductsHOHAL page from when the Q-number was minted), andcarrierQuoteNumberset to the newly-minted duplicate Q-number (orNULLif Progressive did not return one — rare edge). - If NamedInsured is rejected or the workflow cursor never reaches
ProductsHO, the handler throwsDuplicateQuoteIncompleteError, persists astatus='error'audit row (withmetadata.failureReason+metadata.failedFieldEdits), and the controller returnssuccess: 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
| Param | Required | Notes |
|---|---|---|
sessionId | yes | Fastlane session UUID. Must exist so GET /session/:sessionId/initialize succeeds (Redis → CRN fallback). |
carrierId | yes | 377 resolves to Progressive Home via ghcms-carrier-mappings.seed.ts. |
lob | yes | Home. |
state | technically optional | If present, overrides CRN's property state when calling Progressive. If omitted, the handler falls back to crnData.propertyAddress.state. Wrong state → Progressive rejects eligibility. |
q | yes | DA-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-driven | yes | Must be true. Relaxed gate: no longer requires mock=true or DEV mode. |
syncId, dup-bootstrapped | no (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-postgresup (Docker). .envcontains validAPI_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_numbercolumn onfastlane.carrier_quote_sessions(applied by20260420160000_add_progressive_origin_quote_tracking).
Component Map (workspace-relative paths)
| Layer | Path |
|---|---|
| CRN lookup | libs/apis/crn-legacy-db/src/lib/services/crn-progressive-home-lookup.service.ts |
| ACORD parser | libs/apis/crn-legacy-db/src/lib/utils/progressive-home-acord-parser.ts |
| CRN domain types | libs/apis/crn-legacy-db/src/lib/types/crn-progressive-home.types.ts |
| CRN → domain mapper | libs/apis/carriers/progressive/src/lib/application/services/crn-progressive-home-request.mapper.ts |
| Duplicator orchestrator | libs/apis/carriers/progressive/src/lib/application/services/progressive-quote-duplicator.service.ts |
| Gateway handler | apps/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 |
| DTO | libs/apis/carriers/progressive/src/lib/application/dtos/initial-quote.dto.ts |
| Frontend bootstrap hook | apps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/use-initial-quote-bootstrap.ts |
| Frontend API client | apps/fastlane-portal/src/app/pages/carriers/progressive/home/services/progressive-initial-quote-api.ts |
| Config-driven gate | apps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.ts |
| Config-driven context | apps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/config-driven-flow-context.tsx |
Prisma schema (CarrierQuoteSession.originCarrierQuoteNumber) | prisma/schema.prisma |
| Migration | prisma/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:
- Loading state for ~60–180s while bootstrap runs.
- URL rewrites to include
&syncId=...&workflowNode=ProductsHO&dup-bootstrapped=true.qbecomes the new duplicate Q-number if Progressive issued one; otherwiseqstays as the DA Q-number butdup-bootstrapped=trueprevents re-firing. - Config-driven Products page renders.
- DevTools Network: exactly one
POST /progressive/home/initial-quote, followed byPOST /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 string | Meaning | Action |
|---|---|---|
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:
sessionIdmust be a UUID.daQuoteNumbermust 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):
- Root redirect dropping query params. The app's root route in
apps/fastlane-portal/src/app/app.tsxuses<Navigate to={defaultRoute} replace />to redirect/to/carriers/<carrier>/<lob>/. If thatNavigatedoesn't carrylocation.search, everything except params hydrated into Zustand (sessionId,lob,carrierId,state) gets stripped — includingqandconfig-driven=true. The fix isto={`${defaultRoute}${location.search}`}. Verify by landing on/?q=...&config-driven=trueand checking the resulting URL still has those params after the redirect. config-driven=truemissing or typo'd. The gate is inapps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.ts. Any other value short-circuits the entire config-driven shell.sessionIdmissing / ZustandsessionUuidnever populated. The bootstrap hook waits forsessionUuidbefore calling. CheckuseSessionInitlogs.- URL already has
dup-bootstrapped=trueorsyncId— the hook intentionally skips. Strip those from the URL and reload. qdoesn't match^Q\d{6,12}$. The guard inuse-initial-quote-bootstrap.tstreats 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:
stateURL param disagrees with the CRN property address state.- CRN data was thin and
CrnProgressiveHomeRequestMapperfell back to defaults that don't match (year built, sqft, foundation, exterior walls). InspectValidationDetailson 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:
- Is the frontend passing
syncIdin thestep-configrequest body? Checkapps/fastlane-portal/src/app/pages/carriers/progressive/home/dev/use-config-driven.tsand the step-config API call. - Is the cache TTL (
SESSION_TTL_MS = 30 * 60 * 1000) expired? Fresh syncs should not hit this, but very slow UI flows can. - 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 = NULLmetadata.syncId— the Progressive session that stalledmetadata.workflowNode— last known workflow node (e.g.NamedInsured)metadata.currentPage— the HALCurrentPageNavmetadata.failureReason— stringified error messagemetadata.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:
SELECT metadata FROM fastlane.carrier_quote_sessions WHERE "quoteId"='<sessionId>' AND carrier='PROGRESSIVE' AND lob='HOME';— readfailedFieldEdits.- Tail
logs/progressive/for thatsyncId. - Search the file for
HasEdit":true— the questions next to it are the exact fields Progressive rejected. - If
PhoneNumberis flagged: checkapplicant.phonefrom the CRN ACORD parse.UsPhoneNumber.fromRawstrips a leading "1" but throws on anything else that can't be normalized to 10 digits. - If
DateOfBirthis flagged: theProgressiveDateVO expectsYYYY-MM-DDorYYYYMMDD; anything else throws before the HTTP call even goes out. - If the last known page is
NamedInsuredbut 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= sessionIdcarrier= PROGRESSIVElob= HOMEoriginCarrierQuoteNumber= daQuoteNumberstatus=activemetadata.syncIdis 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 usePROGRESSIVE_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_NUMBERis 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
| Operation | Idempotent? |
|---|---|
POST /initial-quote with same (sessionId, daQuoteNumber) and a saved status='active' row with a cached metadata.syncId | Yes — 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 missing | No — will attempt duplication again (by design). |
| CRN snapshot fetch | Yes — cached via external_quote_responses upsert. |
| Progressive NamedInsured submit | No — always creates a new Progressive session. The handler upserts per (quoteId, carrier, lob) so a failed row is overwritten on retry. |