Progressive Home SF Sync — Debugging & Known Debts
Reference for developers working on the Salesforce sync flow for Progressive Home. Covers the three debug mechanisms available today, when each one is useful, and the known data-quality debts that need to be resolved before we can fully trust what ends up in Salesforce.
Debug Mechanisms
1. VITE_SF_SYNC_DEV_MODE — Unified dev mode (frontend + backend)
Type: Shared env var — read by both Vite (import.meta.env) and the NestJS gateway (process.env)
Files:
apps/fastlane-portal/src/app/pages/carriers/progressive/home/config-driven/step-overrides/checkout/checkout-step.tsxapps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/progressive-home-salesforce-sync-orchestrator.service.ts
When set to true this single flag activates two behaviors simultaneously:
Frontend: Clicking Submit on the Checkout step calls the devTriggerSfSync
endpoint and does not execute the actual Progressive bind:
if (import.meta.env.VITE_SF_SYNC_DEV_MODE === 'true') {
const syncResult = await devTriggerSfSync(stepConfig.syncId);
// logs result, sets form error with status, returns early
return;
}
Backend: The LaunchDarkly feature flag check is bypassed — the SF sync always runs regardless of the LD flag state:
if (process.env.VITE_SF_SYNC_DEV_MODE === 'true') {
return true; // skip LD evaluation
}
Use when: You want to test/iterate on the SF sync payload (builders, normalizers, orchestrator) without committing a live insurance policy, or when LD is not configured locally and you need to force-enable the sync.
Production safety: The devTriggerSfSync API endpoint is guarded server-side
and returns an error when NODE_ENV === 'production'. This variable must be
false or unset in production.
2. SF_SYNC_LOG_PAYLOAD — Verbose PII-containing payload logs
Type: Backend env var
Files affected:
apps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/salesforce-sync-client.service.tsapps/apis/fastlane-api-gateway/src/app/salesforce-sync/services/progressive-home-salesforce-sync-orchestrator.service.ts
When set to true, the gateway logs:
- The raw JSON sent to the Salesforce microservice (
policyRequest+propertyRequests). - A structured human-readable breakdown of the outgoing payload: named insureds, property address, mortgagee details, coverage limits.
Both log blocks are wrapped in:
if (process.env.SF_SYNC_LOG_PAYLOAD === 'true') { ... }
Use when: You need to compare what we're sending to SF against what actually
lands in the Salesforce record. Look for [PH-SF-SYNC] and
[SF-SYNC][OUTGOING-PAYLOAD] log prefixes in the gateway output.
Production safety: These logs contain full PII (names, addresses, loan
numbers). Must be false or unset in production.
Local .env Reference
Both flags should be set to true for full local debugging visibility:
# Dev mode: frontend skips real bind + backend bypasses LaunchDarkly
VITE_SF_SYNC_DEV_MODE=true
# Logs full PII payload to gateway console (names, addresses, raw JSON)
SF_SYNC_LOG_PAYLOAD=true
In production both must be false or absent.
Known Data Debts — Coverage Normalizer Defaults
The following constants in
apps/apis/fastlane-api-gateway/src/app/salesforce-sync/mappers/progressive-home-coverage-normalizer.ts
inject assumed values into SF when CRN does not store the real data. They
produce incorrect or misleading SF records and should be eliminated.
PROGRESSIVE_HOME_MEDICAL_PAYMENTS_DEFAULT = 5000
Problem: CRN's coverages_data does not save the Medical Payments
(Coverage F) amount selected by the user. When the field is absent, the
normalizer falls back to 5000 and medical_payments_home__c always arrives
in SF as '5000' — regardless of what the customer actually chose.
Fix: Save medicalPayments to coverages_data in the portal, or read the
actual Coverage F amount from the Progressive HAL API quote/bind response, so
this constant is never reached.
PROGRESSIVE_HOME_FOUNDATION_COVERAGE_DEFAULT = true
Problem: CRN does not store the foundation coverage flag. The normalizer
defaults it to true, meaning every SF Opportunity receives
Foundation_Coverage__c = true even for policies that do not include foundation
coverage.
Fix: Confirm with Progressive and the SF team whether:
- This field should be omitted from the sync when the value is unknown, or
- The actual boolean can be read from the HAL bind response.
PROGRESSIVE_HOME_LOI_DEFAULT_PCT = 20
Problem: When CRN does not store an explicit Loss-of-Use limit, the
normalizer derives it as 20 % of the dwelling limit. The 20 % is a common
HO3 standard but is still an assumption — the user may have selected a different
value.
Severity: Lower than the others because the derived number is at least proportional to real policy data (dwelling limit). However it is still not the actual selected value.
Fix: Save lossOfUse to coverages_data in the portal so CRN always
provides the real figure and this fallback is never needed.
Salesforce Developer Console — Verifying What Landed in SF
After triggering a sync (real or via VITE_SF_SYNC_DEV_MODE), use the SF
Developer Console to confirm the Opportunity record was actually updated and
the field values match what we sent.
Steps
- Log in to Salesforce Sandbox at https://test.salesforce.com
- Top-right menu → Developer Console
- Open Query Editor (bottom panel) and run SOQL to find the Opportunity:
SELECT Id, policy_number__c, insurance_carrier__c, coverage__c,
medical_payments_home__c, Foundation_Coverage__c,
Water_Backup__c, Sudden_Accidental__c,
Replacement_Cost_Picklist__c, Mortgagee_Bind__c,
Loan_Number__c, first_named_insured__c, qti_bind_date__c
FROM Opportunity
WHERE policy_number__c = '<your-policy-number>'
LIMIT 1
- Compare the returned values field-by-field against what the gateway logged
under
[PH-SF-SYNC](enableSF_SYNC_LOG_PAYLOAD=truefirst).
What to look for
| Symptom | Likely cause |
|---|---|
Field is null when it should have a value | Builder is not setting the field / normalizer returned undefined |
Field has a hardcoded value (e.g. 5000, true) | One of the coverage normalizer defaults is firing — see Known Debts below |
| Opportunity not updated at all | SF sync did not run — check VITE_SF_SYNC_DEV_MODE, LD flag, and gateway logs for [PH-SF-SYNC] |
| HTTP error from microservice | Check SALESFORCE_SERVICE_BASE_URL in .env and that the goosehead-apps SF service is running |
Useful: view all recent Opportunities
SELECT Id, policy_number__c, CreatedDate, LastModifiedDate, insurance_carrier__c
FROM Opportunity
ORDER BY LastModifiedDate DESC
LIMIT 20
References
GitLab — Salesforce Microservice
The SALESFORCE_SERVICE_BASE_URL points to the goosehead-apps service,
which is the internal microservice that receives our SyncPolicyRequest payload
and writes to Salesforce via jsforce.
- Repo: gitlab.com/goosehead-insurance/custom-dev/goosehead-apps
- Endpoint consumed:
POST /sync/syncPolicy
Script — test-sf-opportunity-update.js (preservado)
Script jsforce de diagnóstico rápido para verificar que el usuario DEV de SF tiene permisos para actualizar un Opportunity, y para probar valores de campos directamente contra el sandbox sin pasar por el gateway.
Requiere: jsforce instalado en goosehead-apps (npm install en ese repo).
# desde el root de goosehead-apps
node scripts/test-sf-opportunity-update.js
Sustituir las constantes en el script con credenciales y el Opportunity ID real del test antes de ejecutar:
/**
* Quick diagnostic script — tests if the DEV SF user can update an Opportunity.
* Run: node scripts/test-sf-opportunity-update.js
*/
const jsforce = require('jsforce');
const SF_USER = 'digitalagentintegration@goosehead.com.uat'; // DEV/sandbox user
const SF_PASS = '<password+security-token>'; // never commit real value
const SF_LOGIN_URL = 'https://test.salesforce.com';
const OPPORTUNITY_ID = '<OpportunityId from SF>'; // e.g. 006TI00000OQ534YAD
async function main() {
const conn = new jsforce.Connection({ loginUrl: SF_LOGIN_URL });
console.log('Connecting to Salesforce...');
await conn.login(SF_USER, SF_PASS);
console.log('Connected. UserInfo:', conn.userInfo);
console.log('\nAttempting Opportunity update...');
const payload = {
Id: OPPORTUNITY_ID,
premium__c: 360786,
pmt_to_carrier__c: '0',
down_payment_amount__c: '0',
policy_fee__c: 0,
agreed_personal_liability__c: '500000',
Replacement_Cost_Picklist__c: 'Yes',
loss_of_use__c: '72157',
medical_payments_home__c: '5000',
Sudden_Accidental__c: 'Up to Dwelling Limit',
};
try {
const result = await conn.sobject('Opportunity').update(payload);
console.log('Result:', JSON.stringify(result, null, 2));
if (!result.success) {
console.error('UPDATE FAILED:', result.errors);
} else {
console.log('UPDATE SUCCEEDED');
}
} catch (err) {
console.error('EXCEPTION:', err.message || err);
}
}
main().catch(console.error);
Uso típico:
- Copia el Opportunity ID de SF Developer Console (SOQL query de arriba).
- Ajusta el
payloadcon los valores que quieres probar (p.ej. cambiarSudden_Accidental__cpara verificar que el picklist acepta el valor). - Ejecuta — si
UPDATE SUCCEEDEDel campo acepta ese valor y el usuario tiene permisos. SiUPDATE FAILEDrevisaresult.errorspara el mensaje de validación de SF.
No commitear credenciales reales. Este script era de uso local efímero — las
credenciales deben leerse de .env o pasarse por variable de entorno si se
reutiliza el patrón.
Confluence — SF Post-Bind Sync Validation Matrix
The Safeco Home V3.1 sync validation matrix is the closest documented precedent for how SF post-bind sync is validated across carriers. The field matrix, expected values, and validation steps there are directly applicable as a reference pattern for Progressive Home.
A Progressive Home equivalent of this matrix should be created once the sync is validated end-to-end — it will document the expected SF field values for a known test policy and serve as the acceptance criteria for the feature.
Reference Salesforce Policies (UAT)
These UAT Opportunity records can be used as reference points when validating SF sync behavior and field mapping:
-
Progressive Home (PGR Hme) — Opportunity 006TI00000OQ534YAD Use for debugging Progressive Home SF sync field mapping and coverage normalization.
-
Mercury Home (example) — Opportunity 006TI00000NG7GQYA1 Example Mercury Home opportunity for cross-carrier SF sync field comparison.
Flow Summary
Submit (Checkout)
└─ VITE_SF_SYNC_DEV_MODE=true? ── YES ──► devTriggerSfSync endpoint (no bind)
│
NO │
│ ▼
Progressive VITE_SF_SYNC_DEV_MODE or LD flag?
HAL bind │
│ ▼
└────► Orchestrator
│
├─ Coverage normalizer (⚠ defaults above)
├─ Opportunity builder
├─ Property request builder
│
▼
SF microservice call
│
SF_SYNC_LOG_PAYLOAD=true?
│
YES ──► Logs raw JSON + PII breakdown