Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions config/partners.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# out id = "out-{CODE}-{n}"
# Bare FANTASY402_ / partner-only FANTASY402_SPEN_ auto-upgrade to per-out on seed.
# Secrets: {prefix}BEARER_TOKEN · CUSTOMER_ID · AGENT_ID · PASSWORD · DOMAIN · …
# Desk URL env (book-level): PARTNER_DOMAIN — must resolve via active SKINS[].hosts
# Desk URL env (book-level): DESK_DOMAIN (legacy PARTNER_DOMAIN) — must resolve via active SKINS[].hosts
# (bare book-level DOMAIN env is retired). Prefer TOML url= on each out when known.
# @see https://bun.com/docs/runtime/toml
# @see docs/PARTNER-DOMAIN.md
Expand Down Expand Up @@ -40,7 +40,7 @@ partner_code = "SPEN"
provider = "fantasy402"
env_prefix = "FANTASY402_SPEN_1_"
currency = "USD"
# url must be a host on an *active* skin; omit → PARTNER_DOMAIN / Ultra default
# url must be a host on an *active* skin; omit → DESK_DOMAIN / Ultra default
url = "https://fantasy402.com"
# optional desk BookId — must agree with url host (host preferred)
# book_id = "fantasy402"
Expand All @@ -58,7 +58,7 @@ partner_code = "ASH"
provider = "fantasy402"
env_prefix = "FANTASY402_ASH_1_"
currency = "USD"
# url must be a host on an *active* skin; omit → PARTNER_DOMAIN / Ultra default
# url must be a host on an *active* skin; omit → DESK_DOMAIN / Ultra default
url = "https://fantasy402.com"
# book_id = "fantasy402"
working_balance = 2000
Expand Down
42 changes: 30 additions & 12 deletions docs/PARTNER-DOMAIN.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
# Partner domain architecture

Five interconnected layers. **Seat-capital-shaped** naming; **Kalshi-bot** is
the local SSOT for registry + Fantasy Ultra until a separate seat-capital
service is the only writer.
Five interconnected **seat-ops** layers (financial partner → outs → finance).
Desk identity (host → book → skin → live product) lives in
[`src/domain/`](../src/domain/) — not under seat “partner”.

Machine status: `bun run partner:domain` · `bun run partner:domain -- --json`
Skin matrix: `bun run partner:skins` · Book matrix: `bun run partner:books`
**Kalshi-bot** is the local SSOT for registry + Fantasy Ultra until a separate
seat-capital service is the only writer.

Machine status: `bun run domain:status` · `bun run domain:status -- --json`
(legacy: `partner:domain`)
Skin matrix: `bun run domain:skins` · Book matrix: `bun run domain:books`
Host discover: `bun run domain:host-discover`
Event inventory (Buckeye / Fantasy402 → plive+ezlive):
`bun run partner:watch-events`
Competitions (Plive league → canonical id):
[`src/domain/competitions.ts`](../src/domain/competitions.ts) ·
`resolveCompetition`
Expansion map: `bun run partner:map` ·
Expansion map: `bun run domain:map` ·
[`PARTNER-EXECUTION-EXPANSION.md`](PARTNER-EXECUTION-EXPANSION.md) Code map:
[`src/partner/domain.ts`](../src/partner/domain.ts) · inventory:
[`src/partner/architecture.ts`](../src/partner/architecture.ts) (seat-ops layers) ·
desk matrix [`src/domain/`](../src/domain/) · inventory:
[`PARTNER-FANTASY-ULTRA.md`](PARTNER-FANTASY-ULTRA.md)

### Naming split (desk vs seat)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the heading level at Line 25.

### Naming split (desk vs seat) skips the required h2 level. markdownlint-cli2 reports MD001. Change this heading to ##, or add the missing parent heading.

Proposed fix
-### Naming split (desk vs seat)
+## Naming split (desk vs seat)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Naming split (desk vs seat)
## Naming split (desk vs seat)
🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 25-25: Heading levels should only increment by one level at a time
Expected: h2; Actual: h3

(MD001, heading-increment)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/PARTNER-DOMAIN.md` at line 25, Change the “Naming split (desk vs seat)”
heading from h3 to h2 to satisfy the document’s heading hierarchy and
markdownlint MD001; do not add a parent heading.

Source: Linters/SAST tools


| Concept | Name | Example |
| ------- | ---- | ------- |
| Desk host URL env | **`DESK_DOMAIN`** (legacy `PARTNER_DOMAIN`) | `https://parlay21.com` |
| White-label family | `SkinId` | `ace`, `buckeye` |
| Desk brand under skin | `BookId` | `parlay21`, `fantasy402` |
| Seat financial owner | **Partner** (unchanged) | `SPEN` |
| Betting account | Out | `out-SPEN-1` |

---

## Layers
Expand All @@ -36,7 +52,7 @@ Expansion map: `bun run partner:map` ·
| **partial** | Scaffold / one path works; not full loop |
| **planned** | Named in architecture; not implemented |

Run `bun run partner:domain` for the live component checklist.
Run `bun run domain:status` for the live component checklist.

---

Expand Down Expand Up @@ -245,8 +261,10 @@ bun run serve

| Command | Layer |
| ----------------------------------------------------- | --------------------------------------------------------------------- |
| `partner:domain` | All (status) |
| `partner:map` | Telegram → authorization → provider expansion map (Mermaid) |
| `domain:status` (legacy `partner:domain`) | All (seat-ops status) |
| `domain:skins` / `domain:books` | Desk matrix (hosts → skin / book) |
| `domain:host-discover` | Suggest SkinId from host (never auto-maps) |
| `domain:map` (legacy `partner:map`) | Telegram → authorization → provider expansion map (Mermaid) |
| `partner:toml` | Partner + Accounts (Bun.TOML config seed/export) |
| `partner:health` | Registry + env + risk + ledger freshness |
| `partner:desk-smoke` | Per-out secret readiness + optional signed `login()` |
Expand Down Expand Up @@ -363,14 +381,14 @@ Idempotent: re-run seed updates in place (SQLite `ON CONFLICT DO UPDATE`).
1. out prefix FANTASY402_SPEN_1_BEARER_TOKEN
2. partner prefix FANTASY402_SPEN_BEARER_TOKEN
3. book fallback FANTASY402_BEARER_TOKEN
4. desk URL PARTNER_DOMAIN → SKINS default
4. desk URL DESK_DOMAIN (legacy PARTNER_DOMAIN) → SKINS default
(host → BookId via HOST_TO_BOOK + SkinId via HOST_TO_SKIN)
```

Canonical `env_prefix` is **per-out**: `{BOOK}_{CODE}_{N}_`. Bare `FANTASY402_`
or partner-only `FANTASY402_SPEN_` auto-upgrade on materialize/seed. Keys (not
USER/PASS): `BEARER_TOKEN` · `CUSTOMER_ID` · `AGENT_ID` · `PASSWORD` · `DOMAIN`
· `SKIN` · `CURRENCY`. Desk URL: **`PARTNER_DOMAIN`** (or per-out `*DOMAIN`)
· `SKIN` · `CURRENCY`. Desk URL: **`DESK_DOMAIN`** (legacy `PARTNER_DOMAIN`; or per-out `*DOMAIN`)
must resolve via `SKINS[].hosts` → `BookId` + `SkinId`. Bare book-level DOMAIN
env keys are retired (`RETIRED_BARE_BOOK_DOMAIN_ENVS`). Env prefix `{BOOK}_` is
the adapter env brand (`FANTASY402`), not the desk `BookId` catalog. API base
Expand Down
14 changes: 7 additions & 7 deletions docs/PARTNER-FANTASY-ULTRA.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export FANTASY402_AGENT_ID='…'
export FANTASY402_PASSWORD='…'
# optional — example host; omit to use SKINS-derived Ultra-mapper default
# (requireDefaultUrlForUltraMapper / defaultUrlForSkin)
export PARTNER_DOMAIN='https://BOOK.example' # must be a SKINS[].hosts URL → SkinId
export DESK_DOMAIN='https://BOOK.example' # must be a SKINS[].hosts URL → SkinId (legacy: PARTNER_DOMAIN)
export FANTASY402_SKIN=2
export FANTASY402_CURRENCY=USD
```
Expand Down Expand Up @@ -430,7 +430,7 @@ Code: `PandoraSocket` · `coefficients.ts` · `coefficient-store.ts` ·
Partner → Communication → Accounts/Outs → Assets → Finance.

**Full map + maturity:** [`PARTNER-DOMAIN.md`](PARTNER-DOMAIN.md) ·
`bun run partner:domain`
`bun run domain:status`

## Liquidity sources map (partners / outs / providers)

Expand All @@ -452,16 +452,16 @@ Works for **any** host. Mapped desks come from `SKINS[].hosts` (not a hard-coded
book list).

```bash
bun run partner:host-discover -- --url=https://BOOK.example
bun run domain:host-discover -- --url=https://BOOK.example
# → skin unknown|suggested, stores public HTML/asset URLs under docs/artifacts/host-discover/<host>-urls.json

bun run partner:host-discover -- --url=https://BOOK.example --har=./session.har
bun run domain:host-discover -- --url=https://BOOK.example --har=./session.har
# → merges session URLs into inventory only (no Ultra→skin scoring)

bun run partner:host-discover -- --all
bun run domain:host-discover -- --all
# → every apex host in SKINS[].hosts

bun run partner:host-discover -- --url=https://BOOK.example --compare --json
bun run domain:host-discover -- --url=https://BOOK.example --compare --json
# → optional target + all mapped apex hosts
```

Expand All @@ -480,7 +480,7 @@ bun run partner:host-discover -- --url=https://BOOK.example --compare --json
- Mapped host → definitive score 1.0 (`already_mapped`)
- URL inventory: `docs/artifacts/host-discover/<host>-urls.json`
- Code: `src/partner/host-discover.ts` · `src/partner/host-weighted-score.ts` ·
`tools/partner-host-discover.ts`
`tools/host-discover-cli.ts`

### Out identity boundary (host → skin → live products → adapter)

Expand Down
2 changes: 1 addition & 1 deletion docs/PROTONPASS.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ username/password/url — no `--field`. Multi-field desk secrets use
| `agentID` | Text | `FANTASY402_AGENT_ID` |
| `password` | Hidden | `FANTASY402_PASSWORD` |
| `bearerToken` | Hidden | `FANTASY402_BEARER_TOKEN` (browser JWT; short-lived) |
| `domain` | Text | `PARTNER_DOMAIN` (optional; default from `SKINS[].hosts` via `resolveDeskDomainFromEnv` → SkinId) |
| `domain` | Text | `DESK_DOMAIN` (legacy `PARTNER_DOMAIN`; optional; default from `SKINS[].hosts` via `resolveDeskDomainFromEnv` → SkinId) |
| `skin` | Text | `FANTASY402_SKIN` (optional; default `2`) |
| `currency` | Text | `FANTASY402_CURRENCY` (optional; default `USD`) |

Expand Down
15 changes: 10 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,16 @@
"partner:registry": "bun tools/partner-registry.ts",
"partner:capacity": "bun tools/partner-registry.ts",
"partner:sports": "bun tools/partner-sports.ts",
"partner:domain": "bun tools/partner-domain.ts",
"partner:skins": "bun tools/partner-domain.ts --skins",
"partner:books": "bun tools/partner-domain.ts --books",
"partner:host-discover": "bun tools/partner-host-discover.ts",
"partner:map": "bun tools/partner-domain.ts --map",
"domain:status": "bun tools/domain-cli.ts",
"domain:skins": "bun tools/domain-cli.ts --skins",
"domain:books": "bun tools/domain-cli.ts --books",
"domain:host-discover": "bun tools/host-discover-cli.ts",
"domain:map": "bun tools/domain-cli.ts --map",
"partner:domain": "bun tools/domain-cli.ts",
"partner:skins": "bun tools/domain-cli.ts --skins",
"partner:books": "bun tools/domain-cli.ts --books",
"partner:host-discover": "bun tools/host-discover-cli.ts",
"partner:map": "bun tools/domain-cli.ts --map",
"partner:profile": "bun tools/partner-profile.ts",
"partner:avatars": "bun tools/partner-profile.ts --png",
"partner:toml": "bun tools/partner-toml.ts",
Expand Down
19 changes: 11 additions & 8 deletions src/domain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,26 @@ hand-edit product or gap columns; change [`skins.ts`](skins.ts) instead.
**Invariant:** `active: true` ⇒ `hosts.length ≥ 1`
(`assertActiveSkinsHaveHosts`). **Fingerprint gate:** active skins need
fingerprints or membership in `FINGERPRINT_PENDING_SKINS`
(`assertFingerprintCoverage`). Desk URL env: `PARTNER_DOMAIN` (or per-out
`*DOMAIN`) must be a host in an active skin. Bare book-level DOMAIN env keys are
retired (`RETIRED_BARE_BOOK_DOMAIN_ENVS`). `fantasy402` alias → skin `buckeye`
(`assertFingerprintCoverage`). Desk URL env: **`DESK_DOMAIN`** (legacy
`PARTNER_DOMAIN` dual-read; or per-out `*DOMAIN`) must be a host in an active
skin. Bare book-level DOMAIN env keys are retired
(`RETIRED_BARE_BOOK_DOMAIN_ENVS`). `fantasy402` alias → skin `buckeye`
(mapper token / BookId, not SkinId). Host → `BookId` → `SkinId` (`getBookByHost`
/ `resolveBookId` in [`books.ts`](books.ts)).

```bash
bun run partner:skins
bun run partner:skins -- --json
bun run partner:books
bun run partner:books -- --json
bun run domain:skins
bun run domain:skins -- --json
bun run domain:books
bun run domain:books -- --json
# legacy aliases: partner:skins · partner:books
```

## Unknown host

```bash
bun run partner:host-discover -- --url=https://example.com
bun run domain:host-discover -- --url=https://example.com
# legacy: partner:host-discover
```

Suggests skin from weighted fingerprints (`SKINS[].fingerprints` +
Expand Down
5 changes: 3 additions & 2 deletions src/domain/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export type BookRecord = {
label: string;
};

function asBookId(raw: string): BookId {
/** Brand a known desk book token (tests / interior stamps). Prefer resolveBookId for wire. */
export function asBookId(raw: string): BookId {
const t = raw.trim().toLowerCase();
if (!t) throw new Error('BookId: empty');
return t as BookId;
Expand Down Expand Up @@ -191,7 +192,7 @@ export type BookMatrixRow = {
hosts: readonly string[];
};

/** Machine-readable book matrix for `bun run partner:books`. */
/** Machine-readable book matrix for `bun run domain:books`. */
export function buildBookMatrixRows(): BookMatrixRow[] {
return BOOKS.map(b => ({
bookId: b.id,
Expand Down
3 changes: 3 additions & 0 deletions src/domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ export {
listSkins,
normalizeHost,
normalizeSkinName,
DESK_DOMAIN_ENV,
PARTNER_DOMAIN_ENV,
RETIRED_BARE_BOOK_DOMAIN_ENVS,
isRetiredBareBookDomainEnv,
assertActiveSkinsHaveHosts,
listActiveSkins,
requireDefaultUrlForUltraMapper,
deskDomainFromEnvMap,
resolveDeskDomainFromEnv,
resolveSkinId,
skinOfferedCatalogNames,
Expand Down Expand Up @@ -150,6 +152,7 @@ export {
BOOKS,
BOOK_IDS,
HOST_TO_BOOK,
asBookId,
bookOffersLiveProduct,
bookOffersSkin,
buildBookMatrixRows,
Expand Down
2 changes: 1 addition & 1 deletion src/domain/live-product-endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const STATSCORE_BOOKED_EVENTS = {
} as const;

/**
* Desk-relative API paths for the Fantasy402 Ultra mapper (joined to PARTNER_DOMAIN / SKINS host).
* Desk-relative API paths for the Fantasy402 Ultra mapper (joined to DESK_DOMAIN / SKINS host).
* Not absolute URLs — host comes from skins / env.
*/
export const ULTRA_DESK_API_PATHS = {
Expand Down
32 changes: 27 additions & 5 deletions src/domain/skins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type SkinRecord = {
displayName: string;
description: string;
/**
* Active desks must declare ≥1 host (HOST_TO_SKIN / PARTNER_DOMAIN / discover).
* Active desks must declare ≥1 host (HOST_TO_SKIN / DESK_DOMAIN / discover).
* Inactive = placeholder until hosts + offerings are proven.
*/
active: boolean;
Expand Down Expand Up @@ -373,28 +373,50 @@ export function requireDefaultUrlForUltraMapper(): string {
return url;
}

/** Brand-neutral desk URL env — resolved against SKINS hosts / SkinId mapping. */
/**
* Brand-neutral desk **host** URL env — resolved against SKINS hosts / SkinId.
* Prefer this over seat-partner naming; desk identity is host → book → skin.
*/
export const DESK_DOMAIN_ENV = 'DESK_DOMAIN';

/**
* @deprecated Use {@link DESK_DOMAIN_ENV} (`DESK_DOMAIN`). Still dual-read for one release.
*/
export const PARTNER_DOMAIN_ENV = 'PARTNER_DOMAIN';

/**
* Retired bare-book desk URL env keys — never read.
* Use PARTNER_DOMAIN or per-out/partner `{PREFIX}DOMAIN` instead.
* Use DESK_DOMAIN (or legacy PARTNER_DOMAIN) or per-out `{PREFIX}DOMAIN` instead.
*/
export const RETIRED_BARE_BOOK_DOMAIN_ENVS = ['FANTASY402_DOMAIN'] as const;

export function isRetiredBareBookDomainEnv(key: string): boolean {
return (RETIRED_BARE_BOOK_DOMAIN_ENVS as readonly string[]).includes(key);
}

/**
* Preferred desk URL from env map only (no Ultra default).
* `DESK_DOMAIN` wins over legacy `PARTNER_DOMAIN`.
*/
export function deskDomainFromEnvMap(
envMap: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {}
): string | undefined {
const preferred = envMap[DESK_DOMAIN_ENV]?.trim();
if (preferred) return preferred;
const legacy = envMap[PARTNER_DOMAIN_ENV]?.trim();
if (legacy) return legacy;
return undefined;
}

/**
* Desk URL from env (brand-neutral).
* PARTNER_DOMAIN → SKINS Ultra-mapper default (hosts → SkinId)
* DESK_DOMAIN → legacy PARTNER_DOMAIN → SKINS Ultra-mapper default (hosts → SkinId)
* Bare-book DOMAIN env keys in RETIRED_BARE_BOOK_DOMAIN_ENVS are ignored.
*/
export function resolveDeskDomainFromEnv(
envMap: Record<string, string | undefined> = typeof process !== 'undefined' ? process.env : {}
): string {
return envMap[PARTNER_DOMAIN_ENV]?.trim() || requireDefaultUrlForUltraMapper();
return deskDomainFromEnvMap(envMap) || requireDefaultUrlForUltraMapper();
}

export function skinOffersLiveProduct(skin: string, product: string): boolean {
Expand Down
7 changes: 6 additions & 1 deletion src/institutions/event-store/open-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export function applyEventStoreSchema(db: Database): void {
);
db.run(`CREATE INDEX IF NOT EXISTS idx_skin_events_book ON skin_events (book_id)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_skin_events_last_updated ON skin_events (last_updated)`);
db.run(`CREATE INDEX IF NOT EXISTS idx_skin_events_competition ON skin_events (competition_id)`);
// idx_skin_events_competition requires competition_id — create after migrateEventStoreColumns
// Partner financial registry (outs) — secrets stay in env, not here
db.run(`CREATE TABLE IF NOT EXISTS partners (
id TEXT PRIMARY KEY,
Expand Down Expand Up @@ -213,6 +213,11 @@ export function applyEventStoreSchema(db: Database): void {
migrateSkinEventsInventoryIdentity(db);
migrateSkinEventsBookInventoryUnique(db);
migrateSkinEventsCompetitionIds(db);
if (tableHasColumn(db, 'skin_events', 'competition_id')) {
db.run(
`CREATE INDEX IF NOT EXISTS idx_skin_events_competition ON skin_events (competition_id)`
);
}
migrateSourceEventSelectors(db);
abandonLegacyKalshiInventoryRuns(db);
abandonUnpinnedSourceInventoryRuns(db);
Expand Down
2 changes: 1 addition & 1 deletion src/partner/account-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function fantasyDeskEnvPresence(
*
* Required (any level of the fallback chain):
* *BEARER_TOKEN *CUSTOMER_ID *AGENT_ID *PASSWORD
* Optional: PARTNER_DOMAIN (or per-out/partner *DOMAIN) → host → SkinId; *SKIN *CURRENCY
* Optional: DESK_DOMAIN (legacy PARTNER_DOMAIN) or per-out/partner *DOMAIN → host → SkinId; *SKIN *CURRENCY
* Account id: FANTASY402_ACCOUNT_ID (default partner-dummy)
*/
export function loadFantasy402ProfileFromEnv(
Expand Down
Loading