Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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.

Seat-ops status: `bun run ops:status` · `bun run ops: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 ops: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 ops: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) |
| `ops:status` (legacy `partner:domain`) | Seat-ops architecture checklist |
| `ops:map` (legacy `partner:map`) | Telegram → authorization → provider expansion map (Mermaid) |
| `domain:skins` / `domain:books` | Desk matrix (hosts → skin / book) |
| `domain:host-discover` | Suggest SkinId from host (never auto-maps) |
| `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 ops: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:skins": "bun tools/domain-cli.ts --skins",
"domain:books": "bun tools/domain-cli.ts --books",
"domain:host-discover": "bun tools/host-discover-cli.ts",
"ops:status": "bun tools/ops-cli.ts",
"ops:map": "bun tools/ops-cli.ts --map",
"partner:domain": "bun tools/ops-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/ops-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
20 changes: 12 additions & 8 deletions src/domain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,27 @@ 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
# seat-ops (not desk): bun run ops:status
# 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
3 changes: 2 additions & 1 deletion src/domain/books.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type BookRecord = {
label: string;
};

/** Interior brand only — not a wire parser. Prefer resolveBookId / isBookId at boundaries. */
function asBookId(raw: string): BookId {
const t = raw.trim().toLowerCase();
if (!t) throw new Error('BookId: empty');
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
2 changes: 2 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
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