diff --git a/config/partners.example.toml b/config/partners.example.toml index dcbf50d..2d5fd8e 100644 --- a/config/partners.example.toml +++ b/config/partners.example.toml @@ -14,7 +14,6 @@ # Desk URL env (book-level): DESK_DOMAIN — must resolve via active SKINS[].hosts # (bare book-level DOMAIN env is retired). Prefer TOML url= on each out when known. # Capacity rows: live_products = [ { name, per_bet_max, max_win, active } ] -# (legacy key `skins` still dual-read on parse) # @see https://bun.com/docs/runtime/toml # @see docs/SEAT-OPS.md diff --git a/docs/PARTNER-FANTASY-ULTRA.md b/docs/PARTNER-FANTASY-ULTRA.md index 8c6fc19..50d06d8 100644 --- a/docs/PARTNER-FANTASY-ULTRA.md +++ b/docs/PARTNER-FANTASY-ULTRA.md @@ -439,7 +439,7 @@ Partner → Communication → Accounts/Outs → Assets → Finance. | **Partner** | `partners` | Financial owner (profit split, commission) | | **Account (out)** | `betting_accounts` | Place to bet: provider + limits + `env_prefix` for secrets | | **Skin** (white-label) | `src/domain/skins.ts` | `buckeye`, `ace`, `metallic`, `sts`, `1bv`, `lvaction`, `magnum` | -| **Live product** | `src/domain/live-products.ts` + capacity `meta.skins[]` | `plive` / `ezlive` / `ultralive` / `maglive` (`dark` capacity-only) | +| **Live product** | `src/domain/live-products.ts` + capacity `meta.liveProducts[]` | `plive` / `ezlive` / `ultralive` / `maglive` (`dark` capacity-only) | | **Adapter surface** | `AdapterId` (`fantasy402`, `kalshi`) | DTO token on inventory/book rows — **not** seat `partnerId`; distinct from mapper `AdapterId` (`fantasy-ultra` …) | | **Provider** (legacy env) | env / outs column mirror | Still on outs / env; `fantasy402` is BookId + mapper token → skin `buckeye` | | **Sports map** | `src/domain/` + `provider_sport_mappings` seed | Live-product keys primary; optional legacy `fantasy402` dual-write | @@ -488,14 +488,13 @@ On `seedFantasy402FromEnv` / `upsertBettingAccount` (`partner/out-identity.ts`): 1. Resolve host via `getSkinByHost(url)` → white-label `skinId` (unknown host → reject; add to `SKINS[].hosts`) -2. Parse capacity from `meta.liveProducts` (dual-**read** legacy `meta.skins` - only when `liveProducts` is absent) +2. Parse capacity from `meta.liveProducts` only (legacy `meta.skins` ignored) 3. Assert capacity ⊆ skin `offeredLiveProducts` (plus legacy `dark` / numeric Ultra wire) 4. Derive `AdapterBinding`: `adapterId` (`fantasy-ultra` | `kalshi` | `unmapped`) + `mapperKind` + `bookEnvToken` 5. Stamp `skinId`, `mapper`, `liveProducts`, `defaultLiveProduct` only — - writers **drop** legacy `meta.skins` / `defaultSkin` (no dual-write) + writers **drop** any legacy `meta.skins` / `defaultSkin` 6. `getPartnerAdapter` selects Fantasy Ultra when `adapterId === "fantasy-ultra"` diff --git a/docs/SEAT-OPS.md b/docs/SEAT-OPS.md index 401504b..2d12543 100644 --- a/docs/SEAT-OPS.md +++ b/docs/SEAT-OPS.md @@ -419,7 +419,7 @@ url = "https://fantasy402.com" # book_id = "fantasy402" working_balance = 20000 vault_id = "vault-out-SPEN-3" -skins = [ +live_products = [ { name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }, ] ``` diff --git a/src/domain/README.md b/src/domain/README.md index d00fe56..ef79a6a 100644 --- a/src/domain/README.md +++ b/src/domain/README.md @@ -133,13 +133,12 @@ Host is the gateway on account seed/upsert via **OutIdentity** 1. `getSkinByHost(url)` → `SkinId` (unknown host → reject) 2. `getBookByHost(url)` → desk `BookId` (optional stamp) -3. Parse capacity from `meta.liveProducts` (dual-**read** legacy `meta.skins` - only when `liveProducts` is absent) +3. Parse capacity from `meta.liveProducts` only (legacy `meta.skins` ignored) 4. Assert capacity ⊆ `offeredLiveProducts` (+ `dark` / numeric Ultra wire) 5. Derive `AdapterBinding` (`adapterId`: `fantasy-ultra` | `kalshi` | `unmapped`) 6. Stamp `skinId`, `bookId`, `mapper`, `liveProducts`, `defaultLiveProduct` - only — writers **drop** legacy `meta.skins` / `defaultSkin` (no dual-write) + only — writers **drop** any legacy `meta.skins` / `defaultSkin` 7. `getPartnerAdapter` switches on `adapterId === "fantasy-ultra"` (not partner string) diff --git a/src/domain/books.ts b/src/domain/books.ts index ba7cce8..e16e8e5 100644 --- a/src/domain/books.ts +++ b/src/domain/books.ts @@ -182,9 +182,6 @@ export function bookOffersLiveProduct(bookId: BookId | string, product: string): return skinOffersLiveProduct(skinId, product); } -/** @deprecated use bookOffersLiveProduct */ -export const bookOffersSkin = bookOffersLiveProduct; - export type BookMatrixRow = { bookId: BookId; skinId: SkinId; diff --git a/src/domain/index.ts b/src/domain/index.ts index db5d2f5..65fe3ab 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -119,21 +119,15 @@ export { export { BINDING_STATUSES, LIVE_PRODUCT_SPORT_BINDINGS, - SKIN_SPORT_BINDINGS, listLiveProductSportBindings, - listSkinSportBindings, liveProductHasSportCoverage, liveProductsWithBindings, - skinHasSportCoverage, - skinsWithBindings, type BindingStatus, type LiveProductSportBinding, - type SkinSportBinding, } from './live-product-sport-bindings.ts'; export { DEFAULT_COVERAGE_LIVE_PRODUCT, - DEFAULT_COVERAGE_SKIN, WIDGET_FAVORITES_SPORT_ID, resolveSport, type ResolveSportQuery, @@ -188,7 +182,6 @@ export { BOOK_IDS, HOST_TO_BOOK, bookOffersLiveProduct, - bookOffersSkin, buildBookMatrixRows, formatBooksMatrixText, getBook, diff --git a/src/domain/live-product-sport-bindings.ts b/src/domain/live-product-sport-bindings.ts index 8b25aad..2a62d73 100644 --- a/src/domain/live-product-sport-bindings.ts +++ b/src/domain/live-product-sport-bindings.ts @@ -28,9 +28,6 @@ export type LiveProductSportBinding = { status: BindingStatus; }; -/** @deprecated alias — use LiveProductSportBinding */ -export type SkinSportBinding = LiveProductSportBinding; - type BindingSeed = { sportId: SportId; inventoryBucket: string; @@ -161,9 +158,6 @@ export const LIVE_PRODUCT_SPORT_BINDINGS: readonly LiveProductSportBinding[] = [ ...EZLIVE, ]; -/** @deprecated use LIVE_PRODUCT_SPORT_BINDINGS */ -export const SKIN_SPORT_BINDINGS = LIVE_PRODUCT_SPORT_BINDINGS; - const byProduct = new Map([ ['plive', PLIVE], ['ezlive', EZLIVE], @@ -178,13 +172,6 @@ export function listLiveProductSportBindings( return LIVE_PRODUCT_SPORT_BINDINGS; } -/** @deprecated use listLiveProductSportBindings */ -export function listSkinSportBindings( - liveProduct?: LiveProductId -): readonly LiveProductSportBinding[] { - return listLiveProductSportBindings(liveProduct); -} - export function liveProductHasSportCoverage(liveProduct: LiveProductId, sportId: SportId): boolean { const rows = byProduct.get(liveProduct) ?? []; return rows.some( @@ -192,18 +179,8 @@ export function liveProductHasSportCoverage(liveProduct: LiveProductId, sportId: ); } -/** @deprecated use liveProductHasSportCoverage */ -export function skinHasSportCoverage(liveProduct: LiveProductId, sportId: SportId): boolean { - return liveProductHasSportCoverage(liveProduct, sportId); -} - export function liveProductsWithBindings(): LiveProductId[] { return (['plive', 'ezlive', 'ultralive', 'maglive'] as const).filter( p => (byProduct.get(p)?.length ?? 0) > 0 ); } - -/** @deprecated use liveProductsWithBindings */ -export function skinsWithBindings(): LiveProductId[] { - return liveProductsWithBindings(); -} diff --git a/src/domain/resolve-sport.ts b/src/domain/resolve-sport.ts index 8881c75..4338387 100644 --- a/src/domain/resolve-sport.ts +++ b/src/domain/resolve-sport.ts @@ -15,8 +15,6 @@ export const WIDGET_FAVORITES_SPORT_ID = 214; export type ResolveSportQuery = { /** Live product that owns the binding (plive / ezlive / …). */ liveProduct: string; - /** @deprecated use liveProduct */ - skin?: string; inventoryBucket?: string; apiSportId?: number; widgetSportId?: number; @@ -35,7 +33,7 @@ function asLiveProduct(raw: string): LiveProductId | undefined { } export function resolveSport(query: ResolveSportQuery): ResolvedSport | undefined { - const liveProduct = asLiveProduct(query.liveProduct ?? query.skin ?? ''); + const liveProduct = asLiveProduct(query.liveProduct); if (!liveProduct) return undefined; const rows = listLiveProductSportBindings(liveProduct); @@ -71,6 +69,3 @@ export function resolveSport(query: ResolveSportQuery): ResolvedSport | undefine /** Default live product for Fantasy402/Buckeye compatibility lookups. */ export const DEFAULT_COVERAGE_LIVE_PRODUCT: LiveProductId = 'plive'; - -/** @deprecated use DEFAULT_COVERAGE_LIVE_PRODUCT */ -export const DEFAULT_COVERAGE_SKIN = DEFAULT_COVERAGE_LIVE_PRODUCT; diff --git a/src/partner/finance-cron.ts b/src/partner/finance-cron.ts index b6c6386..7b33f3e 100644 --- a/src/partner/finance-cron.ts +++ b/src/partner/finance-cron.ts @@ -113,7 +113,7 @@ export type FinanceCronOutRow = { partnerCode: string; provider: string; totalPerBetMax: number; - skinCount: number; + productCount: number; liveProducts: Array<{ name: string; perBetMax: number }>; workingBalance: number | null; envPrefix: string; @@ -298,11 +298,11 @@ export function formatFinanceCronReportText(report: FinanceCronReport): string { const v = getPartnerVisual(g.partnerCode); lines.push(` ${g.partnerCode} ${v.hex} capacity=$${g.totalCapacity} outs=${g.outs.length}`); for (const o of g.outs) { - const skinBits = o.liveProducts.map(s => `$${s.perBetMax} ${s.name}`).join(' + '); + const productBits = o.liveProducts.map(s => `$${s.perBetMax} ${s.name}`).join(' + '); const envMark = o.envOk ? 'env✓' : `env✗ ${o.missingKeys.join(',')}`; const login = o.loginOk === true ? ' login✓' : o.loginOk === false ? ` login✗` : ''; lines.push( - ` └── ${o.outId} $${o.totalPerBetMax}${skinBits ? ` (${skinBits})` : ''} ${envMark}${login}` + ` └── ${o.outId} $${o.totalPerBetMax}${productBits ? ` (${productBits})` : ''} ${envMark}${login}` ); } } @@ -371,7 +371,7 @@ export async function runFinanceCron( partnerCode, provider: a.provider, totalPerBetMax: cap.totalPerBetMax, - skinCount: cap.liveProducts.length, + productCount: cap.liveProducts.length, liveProducts: cap.liveProducts.map(s => ({ name: s.name, perBetMax: s.perBetMax, @@ -444,7 +444,7 @@ export async function runFinanceCron( totalPerBetMax: r.totalPerBetMax, workingBalance: r.workingBalance, envOk: r.envOk, - skinCount: r.skinCount, + productCount: r.productCount, extra: { missingKeys: r.missingKeys, loginOk: r.loginOk, diff --git a/src/partner/index.ts b/src/partner/index.ts index 9942b8b..c5ef2a3 100644 --- a/src/partner/index.ts +++ b/src/partner/index.ts @@ -189,7 +189,6 @@ export { BOOKS, BOOK_IDS, DEFAULT_COVERAGE_LIVE_PRODUCT, - DEFAULT_COVERAGE_SKIN, HOST_TO_BOOK, HOST_TO_SKIN, LEGACY_CAPACITY_LIVE_PRODUCTS, @@ -200,12 +199,10 @@ export { RETIRED_BARE_BOOK_DOMAIN_ENVS, deskDomainFromEnvMap, SKIN_IDS, - SKIN_SPORT_BINDINGS, SKINS, SPORTS, SPORT_CATEGORIES, bookOffersLiveProduct, - bookOffersSkin, buildBookMatrixRows, formatBooksMatrixText, getBook, @@ -226,7 +223,6 @@ export { listBooksForSkin, listLiveProductSportBindings, listLiveProducts, - listSkinSportBindings, listSkins, listSports, liveProductHasSportCoverage, @@ -237,11 +233,9 @@ export { resolveBookId, resolveSkinId, resolveSport, - skinHasSportCoverage, skinIdForBook, skinOfferedCatalogNames, skinOffersLiveProduct, - skinsWithBindings, type BindingStatus, type BookId, type BookMatrixRow, @@ -255,7 +249,6 @@ export { type SkinId, type SkinMapper, type SkinRecord, - type SkinSportBinding, type SportCategory, type SportId, type SportRecord, diff --git a/src/partner/ledger.ts b/src/partner/ledger.ts index b6f6b58..eb7a492 100644 --- a/src/partner/ledger.ts +++ b/src/partner/ledger.ts @@ -131,7 +131,8 @@ export function writeDeskSnapshot( totalPerBetMax: number; workingBalance: number | null; envOk: boolean; - skinCount: number; + /** Active live-product capacity row count. */ + productCount: number; currency?: string; extra?: Record; nowMs?: number; @@ -161,7 +162,7 @@ export function writeDeskSnapshot( totalPerBetMax: input.totalPerBetMax, workingBalance: input.workingBalance, envOk: input.envOk, - skinCount: input.skinCount, + productCount: input.productCount, ...(input.extra ?? {}), }), createdAt: nowMs, diff --git a/src/partner/out-capacity.ts b/src/partner/out-capacity.ts index 0955169..5bb3806 100644 --- a/src/partner/out-capacity.ts +++ b/src/partner/out-capacity.ts @@ -49,17 +49,10 @@ export type OutMeta = { partnerCode?: string; /** Shared account balance across live products (out-level). */ workingBalance?: number; - /** @deprecated use defaultLiveProduct */ - defaultSkin?: CapacityWireName; /** Default live-product / Ultra wire for this out. */ defaultLiveProduct?: CapacityWireName; /** Canonical capacity rows (live products). */ liveProducts?: MetaCapacityRow[]; - /** - * Legacy capacity rows (name = live product wire). - * Read-only dual path — writers stamp `liveProducts` only. - */ - skins?: MetaCapacityRow[]; /** * White-label SkinId (buckeye / ace / …) — host gateway identity. * Distinct from capacity live-product names. @@ -168,8 +161,9 @@ export function parseOutMeta(metaJson: string | null | undefined): OutMeta { /** * Resolve active live-product capacity for an account. - * Prefer meta.liveProducts[]; dual-read legacy meta.skins[]; else column fallback. + * Reads meta.liveProducts[] only; falls back to maxStake/maxWin + defaultLiveProduct (or "2"). * (Kept here to avoid import cycle with out-identity — same rules as parseCapacityFromMeta.) + * `skin` column on betting_accounts is ignored (always null on write). */ export function resolveOutCapacity(input: { id: string; @@ -182,8 +176,7 @@ export function resolveOutCapacity(input: { if (input.status && input.status !== 'active') return []; const meta = parseOutMeta(input.metaJson); - const source = - meta.liveProducts && meta.liveProducts.length > 0 ? meta.liveProducts : (meta.skins ?? []); + const source = meta.liveProducts ?? []; const fromMeta = source .map((row): OutCapacityRow | null => { @@ -201,10 +194,7 @@ export function resolveOutCapacity(input: { return fromMeta.filter(s => s.active); } - const defaultName = - meta.defaultLiveProduct?.trim() || - meta.defaultSkin?.trim() || - (input.skin != null && Number.isFinite(input.skin) ? String(input.skin) : '2'); + const defaultName = meta.defaultLiveProduct?.trim() || '2'; return [ { name: defaultName, @@ -325,7 +315,7 @@ export function concentrationByOut( .sort((a, b) => b.share - a.share || a.outId.localeCompare(b.outId)); } -/** Build meta_json capacity (writes liveProducts only; readers dual-read legacy skins). */ +/** Build meta_json capacity (writes liveProducts only; strips legacy skins keys). */ export function buildOutCapacityMeta(input: { liveProducts: OutCapacityRow[]; workingBalance?: number; @@ -356,8 +346,8 @@ export function buildOutCapacityMeta(input: { liveProducts: rows, }; // Never re-emit legacy capacity keys when building fresh meta - delete meta.skins; - delete meta.defaultSkin; + delete (meta as Record).skins; + delete (meta as Record).defaultSkin; if (input.workingBalance != null) meta.workingBalance = input.workingBalance; if (input.vaultId) meta.vaultId = input.vaultId; if (input.partnerCode) meta.partnerCode = input.partnerCode; diff --git a/src/partner/out-identity.ts b/src/partner/out-identity.ts index 1e63584..521b07f 100644 --- a/src/partner/out-identity.ts +++ b/src/partner/out-identity.ts @@ -172,8 +172,9 @@ function rowToCapacity(row: RawCapacityRow): LiveProductCapacity | null { } /** - * Parse capacity rows from meta: prefer `liveProducts`, dual-read legacy `skins`. - * Falls back to column skin + maxStake/maxWin when both arrays empty. + * Parse capacity rows from meta.liveProducts only. + * Falls back to maxStake/maxWin + defaultLiveProduct (or "2") when empty. + * Legacy meta.skins / defaultSkin / betting_accounts.skin are ignored. */ export function parseCapacityFromMeta(input: { maxStake: number; @@ -186,19 +187,14 @@ export function parseCapacityFromMeta(input: { const meta = input.meta; const fromLive = Array.isArray(meta.liveProducts) ? (meta.liveProducts as RawCapacityRow[]) : []; - const fromSkins = Array.isArray(meta.skins) ? (meta.skins as RawCapacityRow[]) : []; - const source = fromLive.length > 0 ? fromLive : fromSkins; - - const parsed = source.map(rowToCapacity).filter((c): c is LiveProductCapacity => c != null); + const parsed = fromLive.map(rowToCapacity).filter((c): c is LiveProductCapacity => c != null); if (parsed.length > 0) { return parsed.filter(c => c.active); } const defaultName = - (typeof meta.defaultLiveProduct === 'string' && meta.defaultLiveProduct.trim()) || - (typeof meta.defaultSkin === 'string' && meta.defaultSkin.trim()) || - (input.skin != null && Number.isFinite(input.skin) ? String(input.skin) : '2'); + (typeof meta.defaultLiveProduct === 'string' && meta.defaultLiveProduct.trim()) || '2'; return [ { @@ -227,7 +223,7 @@ export function liveProductNames(identity: OutIdentity): string[] { /** * Stamp OutIdentity into meta_json. * Writes canonical `liveProducts` + `defaultLiveProduct` only. - * Readers still dual-read legacy `meta.skins` / `defaultSkin`. + * Strips any legacy capacity keys on restamp. */ export function stampOutMeta(identity: OutIdentity, baseMeta?: OutMeta): string { const meta: OutMeta = { @@ -244,8 +240,8 @@ export function stampOutMeta(identity: OutIdentity, baseMeta?: OutMeta): string })), }; // Drop legacy capacity mirrors if re-stamping over old meta - delete meta.skins; - delete meta.defaultSkin; + delete (meta as Record).skins; + delete (meta as Record).defaultSkin; if (identity.bookId) meta.bookId = identity.bookId; if (identity.workingBalance != null) meta.workingBalance = identity.workingBalance; if (identity.vaultId) meta.vaultId = identity.vaultId; @@ -289,7 +285,6 @@ export function parseOutIdentity(input: ParseOutIdentityInput): OutIdentity | nu const adapter = adapterBindingForSkin(skinId, input.provider); const defaultLiveProduct = (typeof meta.defaultLiveProduct === 'string' && meta.defaultLiveProduct.trim()) || - (typeof meta.defaultSkin === 'string' && meta.defaultSkin.trim()) || capacity[0]?.liveProduct || '2'; diff --git a/src/partner/registry.ts b/src/partner/registry.ts index cac79aa..f4abe81 100644 --- a/src/partner/registry.ts +++ b/src/partner/registry.ts @@ -63,7 +63,7 @@ export type BettingAccountRow = { currency: string; /** * Legacy SQLite column (nullable). Writers always stamp `null`; - * capacity lives in meta.liveProducts[]. Still dual-read for old rows. + * capacity lives in meta.liveProducts[] only (column ignored on read). */ skin: number | null; /** Non-secret meta only (liveProducts, workingBalance, vaultId, skinId, bookId) */ diff --git a/src/partner/skin-account-guard.ts b/src/partner/skin-account-guard.ts deleted file mode 100644 index 3caafc3..0000000 --- a/src/partner/skin-account-guard.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Write-path guard — thin facade over out-identity boundary. - * Prefer importing from `./out-identity.ts` for new code. - */ - -export { - assertLiveProductsAllowed, - buildSkinMetaFields, - guardAndStampAccountMeta, - isLegacyNumericWire, - resolveSkinForAccountUrl, -} from './out-identity.ts'; diff --git a/src/partner/toml-config.ts b/src/partner/toml-config.ts index d9baaac..28fe1db 100644 --- a/src/partner/toml-config.ts +++ b/src/partner/toml-config.ts @@ -87,8 +87,6 @@ const partnersTomlOutSchema = z vault_id: z.string().optional(), vaultId: z.string().optional(), status: z.enum(['active', 'inactive', 'pending']).or(z.string()).optional(), - /** @deprecated capacity rows — prefer live_products */ - skins: z.array(partnersTomlSkinSchema).optional(), live_products: z.array(partnersTomlSkinSchema).optional(), liveProducts: z.array(partnersTomlSkinSchema).optional(), }) @@ -572,7 +570,7 @@ export function materializePartnersToml(doc: PartnersTomlDoc): { } } - const capacityRows = (o.live_products ?? o.liveProducts ?? o.skins ?? []) as PartnersTomlSkin[]; + const capacityRows = (o.live_products ?? o.liveProducts ?? []) as PartnersTomlSkin[]; const parsedCapacity = capacityRows .map(parseSkin) .filter((s): s is OutCapacityRow => s != null); @@ -795,7 +793,7 @@ function outSig(a: BettingAccountRow): Record { maxStake: a.maxStake, maxWin: a.maxWin, currency: a.currency, - liveProducts: meta.liveProducts ?? meta.skins ?? null, + liveProducts: meta.liveProducts ?? null, workingBalance: meta.workingBalance ?? null, vaultId: meta.vaultId ?? null, partnerCode: meta.partnerCode ?? null, @@ -1010,11 +1008,7 @@ export function buildPartnersTomlFromRows( const partnerCode = String(rowMeta.partnerCode ?? codeByPartnerId.get(a.partnerId) ?? '').toUpperCase() || undefined; - const capacityRaw = Array.isArray(rowMeta.liveProducts) - ? rowMeta.liveProducts - : Array.isArray(rowMeta.skins) - ? rowMeta.skins - : []; + const capacityRaw = Array.isArray(rowMeta.liveProducts) ? rowMeta.liveProducts : []; const liveProducts: PartnersTomlSkin[] = capacityRaw.flatMap(s => { if (!s || typeof s !== 'object') return []; const r = s as Record; @@ -1041,7 +1035,7 @@ export function buildPartnersTomlFromRows( typeof rowMeta.workingBalance === 'number' ? rowMeta.workingBalance : undefined, vault_id: typeof rowMeta.vaultId === 'string' ? rowMeta.vaultId : undefined, status: a.status, - // Prefer live_products in exported TOML; dual-read still accepts skins on parse + // Prefer live_products in exported TOML live_products: liveProducts.length > 0 ? liveProducts diff --git a/tests/partner/dashboard-data.test.ts b/tests/partner/dashboard-data.test.ts index 0396c07..98b7263 100644 --- a/tests/partner/dashboard-data.test.ts +++ b/tests/partner/dashboard-data.test.ts @@ -25,7 +25,7 @@ partner_code = "SPEN" provider = "fantasy402" env_prefix = "FANTASY402_" working_balance = 5000 -skins = [ +live_products = [ { name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }, ] `; diff --git a/tests/partner/execution/cancel.test.ts b/tests/partner/execution/cancel.test.ts index 3e903fe..b787eab 100644 --- a/tests/partner/execution/cancel.test.ts +++ b/tests/partner/execution/cancel.test.ts @@ -59,7 +59,7 @@ function database(): Database { skin: null, metaJson: JSON.stringify({ partnerCode: 'SPORTS', - skins: [{ name: 'main', active: true, perBetMax: 5, maxWin: 20 }], + liveProducts: [{ name: 'main', active: true, perBetMax: 5, maxWin: 20 }], }), }, NOW diff --git a/tests/partner/execution/kalshi-live.test.ts b/tests/partner/execution/kalshi-live.test.ts index f61656e..ac5b96e 100644 --- a/tests/partner/execution/kalshi-live.test.ts +++ b/tests/partner/execution/kalshi-live.test.ts @@ -263,7 +263,9 @@ function setup(variant?: "inactive" | "partner" | "provider"): Database { skin: null, metaJson: JSON.stringify({ partnerCode: "SPORTS", - skins: [{ name: isFantasy ? "ezlive" : "main", perBetMax: 5, maxWin: 20, active: true }], + liveProducts: [ + { name: isFantasy ? "ezlive" : "main", perBetMax: 5, maxWin: 20, active: true }, + ], ...(isFantasy ? { skinId: "buckeye", bookId: "fantasy402" } : {}), }), }, NOW_MS); diff --git a/tests/partner/finance-cron.test.ts b/tests/partner/finance-cron.test.ts index 1a401f8..7b5c57d 100644 --- a/tests/partner/finance-cron.test.ts +++ b/tests/partner/finance-cron.test.ts @@ -23,7 +23,7 @@ id = "out-SPEN-1" partner_code = "SPEN" provider = "fantasy402" env_prefix = "FANTASY402_" -skins = [ +live_products = [ { name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }, { name = "dark", per_bet_max = 1000, max_win = 5000, active = true }, ] diff --git a/tests/partner/ingest-tickets.test.ts b/tests/partner/ingest-tickets.test.ts index 506b3fe..2c3a0ad 100644 --- a/tests/partner/ingest-tickets.test.ts +++ b/tests/partner/ingest-tickets.test.ts @@ -64,7 +64,7 @@ id = "out-SPEN-1" partner_code = "SPEN" provider = "fantasy402" env_prefix = "FANTASY402_SPEN_1_" -skins = [{ name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }] +live_products = [{ name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }] `; describe("partner ticket ingest + finance totals", () => { diff --git a/tests/partner/out-identity.test.ts b/tests/partner/out-identity.test.ts index 76ca531..c3abdf7 100644 --- a/tests/partner/out-identity.test.ts +++ b/tests/partner/out-identity.test.ts @@ -38,7 +38,7 @@ describe('out-identity boundary', () => { expect(stamped).toContain('"bookId":"fantasy402"'); }); - test('dual-read legacy skins only (no liveProducts key); stamp writes liveProducts only', () => { + test('legacy meta.skins alone is ignored; empty capacity falls back to maxStake wire "2"', () => { const identity = parseOutIdentity({ id: 'out-X-1', partnerId: 'partner-x', @@ -50,7 +50,9 @@ describe('out-identity boundary', () => { skins: [{ name: 'plive', perBetMax: 100, maxWin: 500, active: true }], }), }); - expect(identity?.capacity[0]?.liveProduct).toBe('plive'); + // Hard-cut: only liveProducts is capacity SSOT + expect(identity?.capacity.map(c => c.liveProduct)).toEqual(['2']); + expect(identity?.capacity[0]?.perBetMax).toBe(100); const stamped = stampOutMeta(identity!); expect(stamped).toContain('"liveProducts"'); expect(stamped).not.toContain('"skins"'); @@ -58,6 +60,24 @@ describe('out-identity boundary', () => { expect(stamped).not.toContain('"defaultSkin"'); }); + test('stamp writes liveProducts only from liveProducts meta', () => { + const identity = parseOutIdentity({ + id: 'out-X-1', + partnerId: 'partner-x', + url: BUCKEYE_URL, + maxStake: 100, + maxWin: 500, + skin: null, + metaJson: JSON.stringify({ + liveProducts: [{ name: 'plive', perBetMax: 100, maxWin: 500, active: true }], + }), + }); + expect(identity?.capacity[0]?.liveProduct).toBe('plive'); + const stamped = stampOutMeta(identity!); + expect(stamped).toContain('"liveProducts"'); + expect(stamped).not.toContain('"skins"'); + }); + test('maglive on buckeye rejected', () => { expect(() => parseOutIdentity({ diff --git a/tests/partner/risk-health.test.ts b/tests/partner/risk-health.test.ts index bcd750a..ea0f6cd 100644 --- a/tests/partner/risk-health.test.ts +++ b/tests/partner/risk-health.test.ts @@ -28,7 +28,7 @@ partner_code = "SPEN" provider = "fantasy402" env_prefix = "FANTASY402_" working_balance = 5000 -skins = [ +live_products = [ { name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }, { name = "dark", per_bet_max = 1000, max_win = 5000, active = true }, ] @@ -47,7 +47,7 @@ describe("risk health", () => { totalPerBetMax: 1500, workingBalance: 5000, envOk: false, - skinCount: 2, + productCount: 2, }); const report = evaluateRiskHealth(db, accounts, { envMap: {}, diff --git a/tests/partner/toml-config.test.ts b/tests/partner/toml-config.test.ts index 6372ace..e0b5690 100644 --- a/tests/partner/toml-config.test.ts +++ b/tests/partner/toml-config.test.ts @@ -71,7 +71,7 @@ describe('partners TOML (Bun.TOML)', () => { }); - test('legacy skins = still dual-read on parse', () => { + test('legacy TOML skins key is ignored (live_products only)', () => { const doc = parsePartnersToml(` [[partners]] code = "SPEN" @@ -85,8 +85,11 @@ url = "https://fantasy402.com" skins = [{ name = "ezlive", per_bet_max = 500, max_win = 2500, active = true }] `); const m = materializePartnersToml(doc); - expect(m.accounts[0]?.maxStake).toBe(500); + // Hard-cut: skins table ignored → fallback wire "2" at $0 + expect(m.accounts[0]?.maxStake).toBe(0); expect(m.accounts[0]?.skin).toBeNull(); + expect(m.accounts[0]?.metaJson).toContain('"liveProducts"'); + expect(m.accounts[0]?.metaJson).toContain('"2"'); }); test('materialize stamps bookId from url; matching book_id ok; conflict throws', () => { diff --git a/tests/research/trading-order.test.ts b/tests/research/trading-order.test.ts index 30af4e4..53dca7f 100644 --- a/tests/research/trading-order.test.ts +++ b/tests/research/trading-order.test.ts @@ -255,7 +255,7 @@ function authorizedDatabase(nowMs: number): Database { skin: null, metaJson: JSON.stringify({ partnerCode: "SPORTS", - skins: [{ name: "main", perBetMax: 5, maxWin: 20, active: true }], + liveProducts: [{ name: "main", perBetMax: 5, maxWin: 20, active: true }], }), }, nowMs); const policy: AuthorizationPolicy = { @@ -337,7 +337,7 @@ function fantasyDatabase(): Database { partnerCode: "SPORTS", skinId: "buckeye", bookId: "fantasy402", - skins: [{ name: "ezlive", perBetMax: 5, maxWin: 20, active: true }], + liveProducts: [{ name: "ezlive", perBetMax: 5, maxWin: 20, active: true }], }), }); return db;