Plaid Implementation Services land on Partnership in 4 currencies
Closes the v2-vs-SFDC gap: 4 Impl Services SKUs become quotable on the Partnership pricebook in USD, CAD, EUR, and GBP — with deterministic-id datapoints and a regression test that locks the shape.
listPriceCurrencies grows from ["USD","CAD"] to ["USD","CAD","EUR","GBP"]. pricebookIds gains 4 Partnership entries per SKU. Datasheet gets LIST_PRICE + APPROVAL_PRICE L0–L3 rows for each new (SKU × currency).
LIST_PRICE rows keep their legacy ids. CPQ USD + CPQ CAD wiring, prices, and tiers are untouched. CPQ EUR/GBP are deliberately not added.
1. Why this exists
v1 and Salesforce already let reps quote the 4 Impl Services SKUs on Partnership in USD, CAD, EUR, and GBP. v2 onboarding was the lone outlier: its productSpec only advertised CPQ USD + CPQ CAD, so the Partnership pricebook never appeared in the UI for these SKUs.
LIST_PRICE row per SKU (pricebook 42b3ff71…) — but no productSpec ever consulted it, and no CAD/EUR/GBP companions existed.
2. The SKUs in scope
| SKU | Product ID | List | L0 | L1 | L2 | L3 |
|---|---|---|---|---|---|---|
| Impl Services (Standard) — 3 mo | d00540ac…203403 | 60,000 | 60,000 | 30,000 | 15,000 | 0 |
| Impl Services (Custom) — 4 mo | e0598304…2b33cc | 80,000 | 80,000 | 40,000 | 20,000 | 0 |
| Impl Services (Custom) — 5 mo | 205c9927…d7f0b7 | 100,000 | 100,000 | 50,000 | 25,000 | 0 |
| Impl Services (Custom) — 6 mo | f26bb12f…36cc43 | 120,000 | 120,000 | 60,000 | 30,000 | 0 |
These same numeric values are written into every target currency. CAD/EUR/GBP rows just swap the currency field.
3. The availability matrix
Each cell shows what each (SKU × pricebook) pair looks like after this PR. The two CPQ EUR/GBP columns are intentionally not wired — they're a separate scope.
+ approval × 16
+ legacy USD list
+ approval × 16
+ legacy USD list
+ approval × 16
+ legacy USD list
+ approval × 16
+ legacy USD list
Per-SKU Partnership row math: 1 legacy USD LIST_PRICE (kept) + 3 new LIST_PRICE (CAD/EUR/GBP) + 4×4 = 16 new APPROVAL_PRICE (USD/CAD/EUR/GBP × L0–L3) = 20 Partnership rows per SKU. Across 4 SKUs: 80 datapoints, of which 4 are legacy and 76 are new.
4. What changes, file by file
pricebookIds, expand listPriceCurrencies to ["USD","CAD","EUR","GBP"]. Both plaid/ and plaid-test-org/ are real files — they must move in lockstep.
LIST_PRICE rows and USD/CAD/EUR/GBP APPROVAL_PRICE L0–L3 rows are added. plaid-test-org/datasheet.json is a symlink to plaid/datasheet.json, so one write covers both orgs.
scripts/penguin/2026_06_18_…). Re-running is a no-op. Computes deterministic uuidv5 ids, reads CPQ USD as the source of truth for approval tier values, and refuses to proceed if the symlink invariant or CPQ EUR/GBP exclusion is violated.
5. How the deterministic ids work
New datapoint ids are not random. They're generated via uuidv5(name, namespace) using two fixed namespaces — one per datapoint type — so the same (productId, pricebookId, [tier]) tuple always resolves to the same id, on any machine, in any re-run.
The pre-existing Partnership USD LIST_PRICE rows predate this scheme, so they keep their legacy ids. The test asserts presence/value/uniqueness for those rows but deliberately does not pin the id — that's the only exception in the entire spec.
6. How the backfill script protects itself
The script in scripts/penguin/… is the only piece of executable code in the PR. It produced the JSON edits and is checked in so it remains re-runnable. Key invariants it enforces before writing:
plaid-test-org/datasheet.json must be a symlink pointing at ../plaid/datasheet.json. If someone replaced it with a real file, the script refuses to run rather than silently writing only one copy.
pricebookIds already mentions the CPQ EUR or CPQ GBP pricebook, the script throws — its scope explicitly excludes those, and finding them means something else has drifted.
APPROVAL_PRICE rows (not hardcoded). CPQ USD LIST_PRICE and the legacy Partnership USD LIST_PRICE are both verified against the expected numeric list price before any writes happen.
upsertDatapoint returns one of created / updated / unchanged. A second run reports all 76 rows as unchanged and skips the file write entirely. A final pass asserts no duplicate ids exist in the entire datasheet.
Excerpt — id generation and upsert
function computeApprovalPriceDpId(productId, pricebookId, tier) {
return uuidv5(
`APPROVAL_PRICE|${productId}|${pricebookId}|${tier}`,
APPROVAL_PRICE_DP_ID_NAMESPACE,
);
}
function upsertDatapoint(datasheet, datapoint) {
const matches = findDatapoints(datasheet, { ...tags });
if (matches.length > 1) throw new Error("refusing to upsert: duplicate selector");
if (matches.length === 0) { datasheet.push(datapoint); return 'created'; }
if (JSON.stringify(matches[0]) === JSON.stringify(datapoint)) return 'unchanged';
datasheet[datasheet.indexOf(matches[0])] = datapoint; return 'updated';
}
7. The lock test
The new datasheet.partnershipImplServices.test.ts intentionally hardcodes every deterministic id and every expected envelope, rather than re-deriving them from the generator. That makes the test a true regression lock: a future change that alters either the id formula or the price shape will fail the test rather than silently pass.
listPriceCurrencies is exactly [USD,CAD,EUR,GBP]; pricebookIds is exactly the 2 CPQ + 4 Partnership ids; CPQ EUR/GBP are explicitly absent. Plaid and plaid-test-org are pinned identical.
LIST_PRICE with the right value/currency/pageId; exactly four APPROVAL_PRICE rows with deterministic ids and tiered envelope shape. Per SKU: exactly 20 Partnership rows; zero CPQ EUR/GBP rows.
LIST_PRICE count is asserted to be exactly one — a guard against someone later adding a fresh deterministic-id row alongside the legacy one.
plaid-test-org/datasheet.json is asserted to be a symlink with the correct target. Every id in the entire datasheet is asserted globally unique.
8. Before / after gating
"pricebookIds": [
"6bbd64a3…", // CPQ USD
"2bfb3b00…" // CPQ CAD
],
"listPriceCurrencies": ["USD", "CAD"]
Partnership orphan USD LIST_PRICE sits in the datasheet, unreachable.
"pricebookIds": [
"6bbd64a3…", // CPQ USD
"2bfb3b00…", // CPQ CAD
"42b3ff71…", // Partnership USD
"faf43b56…", // Partnership CAD
"bbc87bef…", // Partnership EUR
"b3556e19…" // Partnership GBP
],
"listPriceCurrencies": ["USD","CAD","EUR","GBP"]
9. What it doesn't change
- No FX conversion. CAD/EUR/GBP prices are the USD numeric value with only the currency code swapped — by design, matching Plaid's SFDC.
- No additions to CPQ EUR or CPQ GBP pricebooks for these SKUs (out of scope).
- No change to the existing Partnership USD
LIST_PRICErows — values, ids, andpageIdare preserved. - No change to CPQ USD or CPQ CAD
LIST_PRICE/APPROVAL_PRICErows; explicitly pinned by the test. - No Prisma migrations, no tRPC route changes, no UI code. Pure onboarding JSON + script + test.
- Other Plaid SKUs (Signal, Platform Support Fee, etc.) are not touched.
10. Risks & rollback
apps/server/src/dealops2/onboarding/plaid*. No live schema, no runtime code paths, no migration. Rollback is reverting the commit; no data cleanup needed because nothing has been written to a database.
LIST_PRICE orphan keeps a legacy id; if anyone later regenerates ids from the deterministic formula and forgets the carve-out, a duplicate-USD-list row would appear. The test guards against this. (2) The symlink invariant is load-bearing — replacing plaid-test-org/datasheet.json with a real file would silently halve future backfills. Both the script and the test refuse to proceed in that case.
11. Testing
From the PR description: in each of USD / CAD / EUR / GBP, on a new v2 quote with pricebook set to Partnership, the 4 SKUs should be selectable at the expected list prices:
- Implementation Services (Standard) — 3 months → 60,000
- Implementation Services (Custom) — 4 months → 80,000
- Implementation Services (Custom) — 5 months → 100,000
- Implementation Services (Custom) — 6 months → 120,000
Each price equals the USD number with only the currency code swapped. Approval metadata should resolve to L0–L3 with thresholds matching the table in §2. Manual screenshots for CAD and GBP are attached to the PR.