Plaid v1↔v2 writeback parity harness
A destructive Salesforce E2E script that generates the same Plaid quote in both Dealops v1 and v2, writes both to SFDC, reads them back, and reports every structural diff with v1 as ground truth.
runPlaidE2EWritebackParityHarness.ts that drives both v1 and v2 quote creation, calls Salesforce writeback on each side, and diffs the readback payloads.
packages/feature-flags/flags.ts. Everything else is new scripts, docs, and checked-in run artifacts.
1. Why this exists
Dealops v2 is being rolled out as the replacement for the v1 (Penguin) pricing flow for Plaid. Before flipping customers over, the team needs confidence that a v2 quote, once written through to Salesforce CPQ, produces the same SBQQ__QuoteLine__c rows and field values that v1 would have produced for the same input.
Unit tests and golden-file fixtures can't reach that far — the truth lives in what Salesforce stores after both sides hit the SFDC writeback path. So this PR adds a destructive end-to-end harness that exercises the real path on disposable prod opportunities, then mechanically compares the readback payloads.
2. How the harness runs
Each case in the input file describes one quote and two Salesforce opportunities. For every case the script does six things, in order:
The same logical quote (a use-case bundle or an explicit SKU list) is materialized twice — once in v1's product/price shape, once in v2's — written to two separate but topologically-equivalent disposable opportunities, then both quote line item sets are read back from Salesforce and diffed row-by-row.
3. Case file shape
The runner accepts JSONL, a JSON array, or { "cases": [...] }. Each case identifies both sides and describes the quote. Two case styles are supported:
Picks every product the v2 pricing spec marks as part of a named Plaid use case. Used for the 20 June sweep over all 11 v2 use cases.
{
"caseId": "income-verification-10k",
"quote": {
"useCase": "Income Verification",
"pricebook": "CPQ",
"currency": "USD",
"defaultVolume": 10000
},
"v1": { "organizationId": "...",
"userId": "...",
"opportunityId": "..." },
"v2": { "organizationId": "...",
"userId": "...",
"opportunityId": "..." }
}
For sheet-driven custom bundles. Supports tiered prices, price/percent/volume ramps, linear ramps, and ramped monthly minimums.
{
"caseId": "custom-identity-skus-10k",
"quote": {
"pricebook": "CPQ", "currency": "USD",
"defaultVolume": 10000,
"products": [
{ "productCode": "AUT - UF", "volume": 10000 },
{ "productCode": "IDV1 - UF", "volume": 10000,
"tiers": [...],
"flattenTierStrategy": "blended" }
]
},
"v1": { ... },
"v2": { ... }
}
Product resolution
| Field | Resolves | When to use |
|---|---|---|
productCode | Same code on both sides | Default — works for most Plaid SKUs. |
v1ProductId | v1 directly | v1 has duplicate product codes. |
v2ProductSpecId | v2 directly | v2 spec has ambiguous names. |
name | Disambiguator | Combined with code when needed. |
4. Safety surface
This script writes to real Salesforce. The PR description leads with a "safety / false-positive audit" for that reason. Four guards stack on top of each other:
--writes-to-prod-sfdc as a bare flag. --writes-to-prod-sfdc=false is explicitly rejected, not silently treated as falsy.
--clean-opportunities-before-writeback is also bare-only. It deletes existing OLIs and clears the primary quote before each writeback. Intended for disposable opps only.
SBQQ__RequiredBy__c is compared by normalized parent/child shape, not Salesforce ID strings — so a parent-child relationship that exists on both sides doesn't false-positive just because the IDs differ.
What counts as "acceptable" vs structural
| Field | Treatment | Why |
|---|---|---|
SBQQ__Number__c | Acceptable | Salesforce-assigned display order. |
SBCF_Net_Unit_Price__c | Acceptable | Derived formula field. Source price/discount stay structural. |
SBQQ__Quote__c, SBQQ__SegmentKey__c, SBQQ__Dimension__c | Acceptable | Run-specific IDs that can't be expected to match across orgs. |
| Price fields within ±1¢ | Acceptable | Rounding noise, not a logic diff. |
| Everything else listed below | Structural | Row presence, pricing, discount, subscription, billing, approval, parent topology. |
5. The 21 June destructive run
The PR also checks in the final prod run that motivated shipping the harness. The input is the Plaid pricing-team sheet, converted to JSONL. S11–S15 were empty templates in the sheet and are skipped.
all-11-clean-pinned/report.v1-ground-truth.md — the human-readable diff the team works from.
Top recurring diff fields (21 June run)
Bar widths are illustrative relative ranking from the PR's "top repeated diffs" summary, not absolute counts. See report.real-diffs.md for exact tallies per field.
How to read a diff
{
"ProductCode": "PILDL-UF",
"SBQQ__CustomerPrice__c": 6.30,
"SBQQ__Discount__c": 0.05,
"SBQQ__DefaultSubscriptionTerm__c": 12,
"SBQQ__RequiredBy__c": "<parent: Plaid Income (LDL)>"
}
{
"ProductCode": "PILDL-UF",
"SBQQ__CustomerPrice__c": 6.30,
"SBQQ__Discount__c": null, // diff
"SBQQ__DefaultSubscriptionTerm__c": null, // diff
"SBQQ__RequiredBy__c": "<no parent>" // diff
}
6. Run artifacts checked in
Two run directories ship in this PR. Each contains both the structured Salesforce readback payloads and the human-readable reports.
| Directory | Cases | Compared | Failed | Diffs | Notes |
|---|---|---|---|---|---|
2026-06-20-all-usecases-10k/ |
11 v2 use cases | 9 | 2 | 290 | Failures: inbound-bank-payments-10k, outbound-bank-payments-10k — v1 couldn't resolve Transfer (Same-day ACH) - High Risk Customers. |
2026-06-21-prod-v1-v2-sheet-cases/all-11-clean-pinned/ |
S1–S10, S16 | 11 | 0 | 448 | Run with --clean-opportunities-before-writeback. Pinned as the canonical reference. |
Output file layout per run
report.v1-ground-truth.md— primary human report; v1 labeled expected, v2 labeled actual.report.real-diffs.md/.json/.jsonl— structural diffs only.report.md— full summary including acceptable-diff counts.report.json/.jsonl— full structured report with raw SFDC payloads.report.cases.formatted.json— pretty-printed per-case echo.input-cases.jsonl— exact input used.
7. What this PR does not change
- No application code changes in
apps/server,apps/client, tRPC routers, or Prisma schemas. - No change to v1 or v2 pricing logic — the diffs are observational, not fixes.
- No change to the Salesforce writeback paths themselves; the harness consumes them as black boxes.
- No CI integration. The script is run manually with
pnpm exec tsxagainst a chosenDATABASE_URL. - The single one-line change in
packages/feature-flags/flags.ts(a Plaid-prod-org enablement flag) is the only edit to non-script, non-doc code in the PR.
8. Risks & open questions
--writes-to-prod-sfdc and --clean-opportunities-before-writeback is the main guard. Reviewers should confirm the v1/v2 opportunity IDs checked into the run artifacts are the agreed-upon disposable test opps.
Transfer (Same-day ACH) - High Risk Customers by product code. Real diff coverage for inbound/outbound bank payments is therefore missing in that run. The 21 June sheet run compares all 11 of its cases successfully, so the gap is bundle-specific, not harness-wide.
Things worth asking in review
- Should the harness live in
apps/server/src/dealops2/scripts/long-term, or move into a dedicated tooling package once it's stable? - Is there value in a non-destructive "dry-run" mode that stops before the SFDC write — for CI or for safer local exploration?
- The 17k-line JSON artifacts inflate the repo. Worth gitignoring future runs and only checking in markdown summaries plus the pinned run?
- The acceptable-vs-structural classification is hardcoded. Should it be a config file alongside the case input so reviewers can tighten it without editing the script?