Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
c686c7c
docs(spec): Tournamental Bot Arena design (Phase 1 launch + Phase 2 f…
0800tim Jun 7, 2026
d86389d
docs(plan): Phase 1 implementation plan for Open Bot Arena
0800tim Jun 7, 2026
03894b5
feat(auth-sms): add is_bot column to user table for Bot Arena
0800tim Jun 7, 2026
2767c65
feat(game): bot arena DB schema (users.is_bot, brackets.committed_at,…
0800tim Jun 7, 2026
291dd0f
feat(web): Humans / Bots / My Pools tabs on /leaderboard
0800tim Jun 7, 2026
1c30e89
chore(bot-sdk): scaffold @tournamental/bot-sdk package
0800tim Jun 7, 2026
f2340c2
feat(bot-sdk): Bot class with queue/flush + HTTP client with retries
0800tim Jun 7, 2026
7502b2b
feat(bot-sdk): Swarm helper with bounded concurrency
0800tim Jun 7, 2026
923cb40
docs(bot-sdk): eight runnable examples plus README
0800tim Jun 7, 2026
7060030
feat(game): API key DAO with sha256 hashing + academic quota lift
0800tim Jun 7, 2026
6c026f1
feat(operator-swarm): PM2 wrapper for Tim's 1M-bot federated swarm node
0800tim Jun 7, 2026
9a03f32
feat(game): BotOwner + Quota DAOs with sliding hourly window
0800tim Jun 7, 2026
743af04
merge: A8 operator-swarm into bot-arena-launch
0800tim Jun 7, 2026
af8b77d
feat(sage): Tournamental Sage reference bot (Claude-driven)
0800tim Jun 7, 2026
285e2a0
feat(game): FederatedNodeStore DAO (Phase 2 federation registry)
0800tim Jun 7, 2026
3d9ef4d
feat(bot-node): federated Tournamental bot node MVP for WC 2026 launch
0800tim Jun 7, 2026
48601af
feat(game): sorted-pair sha256 merkle tree for Phase 2 federated audit
0800tim Jun 7, 2026
9ca7e8a
feat(bot-mcp): MCP server for the Open Bot Arena
0800tim Jun 7, 2026
3bedc7e
feat(game): wire bot-arena DAOs into GameStore + scope-filtered topN
0800tim Jun 7, 2026
38756b5
docs(bot-arena): add OpenAPI spec, doc 20 cross-reference
0800tim Jun 7, 2026
a0dd6a2
feat(web): /bots/sdk developer documentation page
0800tim Jun 7, 2026
d6ecd53
feat(web): self-service /bots/keys API key issuance
0800tim Jun 7, 2026
c9adb99
feat(web): /developers hub + /bots/node operator guide + Bot Arena nav
0800tim Jun 7, 2026
b1d3cb4
docs(terms): bots welcome but ineligible for cash prize
0800tim Jun 7, 2026
a490b48
feat(game): POST /v1/picks/bulk for bot-arena swarm submissions
0800tim Jun 7, 2026
43c54da
docs(sessions): A4 Bot Arena frontend complete
0800tim Jun 7, 2026
febdbcf
fix(web): correct KeyboardEvent generic on LeaderboardTabs handler
0800tim Jun 7, 2026
0433dfa
feat(web): /bot-arena marketing page
0800tim Jun 7, 2026
0d4f4ae
merge: A4 frontend (leaderboard tabs + /bots/* pages + /developers + …
0800tim Jun 7, 2026
700fd0a
feat(game): federation endpoints (/v1/nodes/register, /commit, /leade…
0800tim Jun 7, 2026
2a9942f
feat(game): kickoff commitment service posts merkle root + stamps bra…
0800tim Jun 7, 2026
86e6de2
feat(web): /bot-arena reframed around 9 June swarm-builder launch
0800tim Jun 7, 2026
3fb0279
feat(seed-bots): deterministic CLI for 18k cosmetic bot seeding
0800tim Jun 7, 2026
13dd099
feat(browser-swarm): browser-tab bot arena with /run page + Web Worke…
0800tim Jun 7, 2026
e27848f
fix(bot-sdk): exports.import.types points to dist/index.d.ts (tsup em…
0800tim Jun 7, 2026
7c2c79d
feat(web): /run gains how-to intro, throughput table, and honest perf…
0800tim Jun 7, 2026
edfe670
fix(browser-swarm): accumulate across button presses + show cumulativ…
0800tim Jun 7, 2026
ebb0ab4
feat(browser-swarm): /run/bots paginated list + per-bot bracket detai…
0800tim Jun 7, 2026
43a7ab5
feat(browser-swarm): real 104-match WC 2026 fixtures plumbed through
0800tim Jun 7, 2026
4fd3b02
feat(press): add 2026-06-07 release for open bot floor (FIFA WC 2026)
0800tim Jun 7, 2026
e291a1d
feat(bot-arena): swarm framing + maths bite + Merkle-OTS-Bitcoin chain
0800tim Jun 7, 2026
bfb909a
feat(run): 'Multiple browsers, one account' section + verify pointer
0800tim Jun 7, 2026
3f98df6
feat(browser-swarm): device_id + fixture-version auto-wipe + multi-de…
0800tim Jun 7, 2026
c02fcf0
feat(verify): clearer Merkle-OTS-Bitcoin chain + pending/confirmed fr…
0800tim Jun 7, 2026
e10e279
docs(bot-arena): add doc 30 browser-swarm + doc 31 merkle/OTS
0800tim Jun 7, 2026
0110ff1
feat(the-bet): frame perfect-bracket challenge + link bot floor release
0800tim Jun 7, 2026
890a900
feat(developers,bots): align all dev surfaces with the swarm + bot fl…
0800tim Jun 7, 2026
d46f44d
docs(bot-arena): add doc 32 perfect-bracket experiment
0800tim Jun 7, 2026
4bcb0bd
feat(browser-swarm): live hashing progress + copyable swarm merkle root
0800tim Jun 7, 2026
13c2a07
docs(bot-arena): README Bot Arena section + doc 17 clarification + se…
0800tim Jun 7, 2026
4653374
feat(browser-swarm): real WC2026 fixtures + WC2026-only personas + da…
0800tim Jun 7, 2026
68d3344
feat(game): OpenTimestamps calendar HTTP client
0800tim Jun 7, 2026
eb95b70
feat(game): swarm_claims table + DAO for browser-swarm federation
0800tim Jun 7, 2026
adae3dd
feat(game): /v1/swarm/commit + leaderboard + proof routes + OTS sched…
0800tim Jun 7, 2026
cb70ac9
feat(browser-swarm): wire federation client to /v1/swarm/commit
0800tim Jun 7, 2026
47bae81
feat(verify): interactive swarm-claim verifier on /verify
0800tim Jun 7, 2026
575e5bf
merge(agent/wipe-stale-swarms): device identity + auto-wipe on fixtur…
0800tim Jun 7, 2026
b27f07e
merge(agent/A1-real-fixtures): real FIFA 2026 fixtures (104 matches),…
0800tim Jun 7, 2026
e64bdc4
merge(agent/A2-merkle-progress): worker hashing-phase progress + swar…
0800tim Jun 7, 2026
64d4814
merge(agent/A3-ots-federation): OpenTimestamps client + /v1/swarm/* e…
0800tim Jun 7, 2026
370fd80
merge(agent/A4-bot-docs): comprehensive bot-arena documentation pack
0800tim Jun 7, 2026
2961b02
merge(agent/A5-press-marketing): press release + bot-arena/run/verify…
0800tim Jun 7, 2026
d20638b
fix(browser-swarm): close A1/A3/A6 cross-branch integration gaps
0800tim Jun 7, 2026
40e234c
feat(auth): WhatsApp deep-link recovery on dead magic-link + bad OTP
0800tim Jun 7, 2026
4cb691a
merge(bot-arena-dev): bring A1-A6 + cleanup + WhatsApp recovery into …
0800tim Jun 7, 2026
1874083
feat(web): /bot-arena reframed as live, not launching-soon
0800tim Jun 7, 2026
522b6c0
fix(web): rewrite /v1/swarm/* to game-service so browser federation c…
0800tim Jun 7, 2026
e3638af
fix(web): rewrite /v1/swarm/* to game-service so browser federation c…
0800tim Jun 7, 2026
1927756
feat(browser-swarm): per-bot cascade resolver renders real team names
0800tim Jun 7, 2026
93441af
feat(browser-swarm): within-swarm uniqueness + user-anchored slider
0800tim Jun 7, 2026
3b16b7f
docs(sessions): A11 Phase 2 polish session note
0800tim Jun 7, 2026
a5f1953
merge: A11 Phase 2 polish (cascade resolver + uniqueness + anchor sli…
0800tim Jun 7, 2026
68c902b
feat(web,browser-swarm): nav swap + storage/strategy/run UX polish + …
0800tim Jun 7, 2026
03726e5
fix(profile,nav): surface silent Supabase-missing failure on Generate…
0800tim Jun 7, 2026
1c39588
feat(bot-sdk): add connect/matches + getOdds/getInjuries/getWeather
0800tim Jun 7, 2026
5b77da1
feat(game): add /v1/bots/keys/issue shared-secret endpoint
0800tim Jun 7, 2026
97cea67
fix(profile): fall back to cookie-session key mint for SMS-OTP users
0800tim Jun 7, 2026
df3de74
chore(bot-sdk): drop publishConfig.provenance for manual publish path
0800tim Jun 7, 2026
df79271
feat(swarms): A13 aggregate-leaderboard plumbing (publish + read + pr…
0800tim Jun 7, 2026
3e5907c
chore(packages): bump 4 republishes to 0.2.0 + strip publishConfig.pr…
0800tim Jun 7, 2026
8cfbc0e
feat(browser-swarm): per-bot cascade resolver renders real team names
0800tim Jun 7, 2026
29f2d3c
feat(browser-swarm): within-swarm uniqueness + user-anchored slider
0800tim Jun 7, 2026
604f745
docs(sessions): A11 Phase 2 polish session note
0800tim Jun 7, 2026
c6cde6b
feat(web,browser-swarm): nav swap + storage/strategy/run UX polish + …
0800tim Jun 7, 2026
600d037
fix(profile,nav): surface silent Supabase-missing failure on Generate…
0800tim Jun 7, 2026
d4fc5cc
feat(bot-sdk): add connect/matches + getOdds/getInjuries/getWeather
0800tim Jun 7, 2026
b4a17ed
feat(game): add /v1/bots/keys/issue shared-secret endpoint
0800tim Jun 7, 2026
6592f5a
fix(profile): fall back to cookie-session key mint for SMS-OTP users
0800tim Jun 7, 2026
e48fc16
chore(bot-sdk): drop publishConfig.provenance for manual publish path
0800tim Jun 7, 2026
f370459
feat(swarms): A13 aggregate-leaderboard plumbing (publish + read + pr…
0800tim Jun 7, 2026
65dfe82
style(leaderboard): fold scope tabs into card + tighten spacing + cap…
0800tim Jun 7, 2026
363bc62
style(leaderboard): single-row tab strip - Humans / Bots / Global / C…
0800tim Jun 7, 2026
f93f90b
feat(bot-arena): photo hero + live stats chips + /v1/swarm/totals end…
0800tim Jun 7, 2026
a2e01db
merge(bot-arena-dev): bring photo hero + ArenaStats + /v1/swarm/total…
0800tim Jun 7, 2026
bbc8b28
feat(home,press): publish 2026-06-07 perfect-bot-bracket release + Bo…
0800tim Jun 7, 2026
3afcd90
feat(web): rewrite clean /whitepaper/<slug> URLs to public index.html
0800tim Jun 7, 2026
7733976
style(home): move Step 1 picks block above the bet feature card
0800tim Jun 7, 2026
01c25d8
fix(home): keep Set picks + Run pool CTAs overlaid on the hero banner
0800tim Jun 7, 2026
526360f
fix(home): bot-arena card on brand gold, drop the off-brand blue accents
0800tim Jun 7, 2026
8d83106
feat(bot-node)!: v0.3.0 regenerate-on-demand schema (16 GB → 50 KB pe…
0800tim Jun 7, 2026
8261182
feat(bots): strategy recalibration + Polymarket odds + incognito gate…
0800tim Jun 7, 2026
bdfa8e5
chore(billion-bot): dashboard server + compose + fixtures for the fed…
0800tim Jun 7, 2026
0958d6f
Merge remote-tracking branch 'origin/main' into feat/bot-arena-launch
0800tim Jun 7, 2026
073a1d0
fix(leaderboard): adopt new matchesAvailableTo(recorded, registeredAt…
0800tim Jun 7, 2026
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,8 @@ data/form-screenshots/
# from disk by /verify/[ts]/[file]/route.ts. Tim 2026-06-04.
apps/web/data/audit/*/snapshot.db
apps/web/data/audit/*/snapshot.db.ots

# billion-bot: packed npm tarballs + runtime data are build/runtime artifacts
apps/billion-bot/*.tgz
apps/billion-bot/data/
apps/game/data-dev/
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,22 @@ Postgres 16 + Redis 7, both Dockerised, brought up via `bash infra/scripts/db-up

Migrations: builders pick the migration tool that fits their stack (Prisma for Node TS surfaces, plain SQL for the producer if it ever writes). Whichever tool, **migrations live in `apps/<service>/migrations/` and are checked in.** Reviewer agent rejects PRs that mutate schema without a migration.

## SDK release process (mandatory when bumping `@tournamental/bot-node`)

External users install the bot SDK via `npm i @tournamental/bot-node` or run `npx tournamental-bot-node`. **Any change to `packages/bot-node/` must follow this checklist or the published SDK drifts behind the source.** The version published to npm is the version users get; nothing automatic mirrors the source bump.

1. **Bump the version** in `packages/bot-node/package.json`. Semver: patch for bugfix, minor for additive API, **major (with a SCHEMA migration note) for any storage-layer change**. The schema is breaking by default — operators upgrading minor versions should not have to wipe their data volume.
2. **Update tests** in `packages/bot-node/test/`. `pnpm --filter @tournamental/bot-node test` must be green before publishing.
3. **Build + pack** locally: `cd packages/bot-node && pnpm run build && pnpm pack`. Produces `tournamental-bot-node-<ver>.tgz`.
4. **Smoke the dependents** (any package that consumes the tarball directly):
- `apps/billion-bot/` — edit `package.json` + `Dockerfile` to reference the new `tournamental-bot-node-<ver>.tgz`, then `rm -rf node_modules && pnpm install && docker compose build --no-cache && docker compose up -d`. Confirm `/stats` on port 4080 reports the new version.
- Any other consumer the orchestrator names in the session note.
5. **Publish to npm**: `npm publish --access public` from `packages/bot-node/`. Requires a token with `@tournamental/*` write scope in `~/.npmrc`. If `npm whoami` returns 401 or publish returns 404 on the scope, **stop and escalate to Tim** — do not skip this step or external users will install a stale version.
6. **Commit** the version bump + new tarball reference + updated docs in one PR titled `feat(bot-node): vX.Y.Z — <what changed>`. The body must list: the schema delta, whether operators need to wipe their data volume, and the rollback story.
7. **Announce** in `IDEAS.md` (or the launch channel post-launch) so operators know to update their `npm i` / pull the new docker image.

A schema migration that requires a wipe (e.g. v0.3.0's regenerate-on-demand collapse) **must** include an `infra/scripts/migrate-bot-node-<from>-to-<to>.md` runbook covering: (a) what to back up first, (b) the wipe commands, (c) the verification steps post-restart.

## Three things to remember

1. **Ship the AR-FR demo first.** Everything else is enabled by it.
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ Domain: **tournamental.com**. Brand expansion when needed: **Tournamental, Verif
- **npm packages live** under [`@tournamental/*`](https://www.npmjs.com/search?q=%40tournamental) -- `spec`, `bracket-engine`, `social-cards`, and `plugin-sdk` (in development).
- **MCP server live** at [`mcp.tournamental.com`](https://mcp.tournamental.com) so Claude, Cursor, Windsurf, and other Model Context Protocol clients can read live Tournamental state.
- **Engineering blog + plugin SDK** -- the engineering log at [`tournamental.com/engineering`](https://tournamental.com/engineering) is now the canonical entry point for builders, and the plugin SDK in [`packages/plugin-sdk/`](packages/plugin-sdk) lets you drop in renderers, scorers, ingest sources, identity providers, share-card pipelines, odds feeds, and affiliate routers without forking the core.
- **Open Bot Arena live** at [`play.tournamental.com/run`](https://play.tournamental.com/run) -- spin up a browser-tab swarm of prediction bots, every pre-kickoff merkle root anchored to Bitcoin via OpenTimestamps for $0. See the Bot Arena section below.

## Bot Arena

The **Open Bot Arena** is Tournamental's open-source, blockchain-anchored experiment: can anyone in the world generate a perfect 104-match FIFA World Cup 2026 bracket using an AI swarm? Anyone can join in 30 seconds from a browser tab, no install, no signup, no payment.

- **Run the swarm:** open [`play.tournamental.com/run`](https://play.tournamental.com/run), click the button, watch your tab spin up one Web Worker per CPU core and grind through bracket after bracket. A 2022-era laptop comfortably runs 100,000 bots through a 104-match bracket in under 10 seconds.
- **Cryptographic anchor:** before every kickoff, each tab publishes a sorted-pair sha256 merkle root committing to all of its bots' picks for that match. The roots are aggregated centrally into a federation root and OTS-anchored to Bitcoin. Cost to us: $0 (the OpenTimestamps calendars cover Bitcoin transaction fees through aggregation).
- **Verifiable end-to-end:** anyone with the audit-export bundle and a Bitcoin full node can reproduce any bot's bracket, verify the merkle inclusion proof, and confirm the commitment existed at-or-before the match's kickoff. The reference verifier ships in `packages/bot-node/src/verifier/` under Apache 2.0.
- **Bots compete, bots do not win money.** Per [terms of service](https://tournamental.com/terms/house-prize#bots), bots are ineligible for cash prizes (Humanness Score floor of 50; bots are 0 by design). Perfect-bracket bots get a badge, a research co-author invitation, and a non-monetary trophy.

The four docs to read, in order:

- [`docs/30-browser-swarm-architecture.md`](docs/30-browser-swarm-architecture.md) -- what a swarm is, how it scales (Web Workers / multi-tab / multi-machine), deterministic regeneration, IndexedDB schema, chalk and Claude strategies, federation client, performance budgets.
- [`docs/31-merkle-and-ots-proofs.md`](docs/31-merkle-and-ots-proofs.md) -- the cryptographic core. Sorted-pair sha256 leaf and pair rules, worked example, the `.ots` file format, Bitcoin upgrade path, the verifier protocol.
- [`docs/32-perfect-bracket-experiment.md`](docs/32-perfect-bracket-experiment.md) -- the user-facing narrative and the maths (~1 in 10^29 for chalk-only, ~1 in 10^44 for uniform-random, why no realistic swarm brute-forces it).
- [`docs/17-vstamp-and-prediction-iq.md`](docs/17-vstamp-and-prediction-iq.md) -- the parallel per-prediction VStamp surface for the human-facing prediction game.

## Build on Tournamental in 20 minutes

Expand Down
15 changes: 15 additions & 0 deletions apps/auth-sms/migrations/0005-add-is-bot.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- 0005-add-is-bot.sql , Bot Arena marker on the user table.
--
-- Why: the Phase 1 Open Bot Arena (see
-- docs/superpowers/specs/2026-06-07-bot-arena-design.md §4.1) needs to
-- distinguish bots from humans at the auth layer so the prize-eligibility
-- gate and the leaderboard scope filter can short-circuit on a single
-- column read. Default 0 backfills existing rows safely.
--
-- Note: auth-sms applies migrations inline via Storage.migrate*() helpers
-- rather than reading these .sql files at runtime. This file is the
-- canonical reference for the migration. The runtime equivalent lives in
-- apps/auth-sms/src/storage.ts inside migrateUserBotColumn().

ALTER TABLE user ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0;
CREATE INDEX IF NOT EXISTS idx_user_is_bot ON user(is_bot);
91 changes: 91 additions & 0 deletions apps/auth-sms/src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,15 @@ export interface UserRecord {
highlevel_contact_id: string | null;
/** Unix seconds when the contact was last synced to HighLevel. NULL if not yet. */
highlevel_synced_at: number | null;
/**
* 1 if this row represents a bot competing in the Open Bot Arena
* (Phase 1, FIFA WC 2026 launch). 0 for human users. Bots are
* ineligible for the cash prize regardless of leaderboard position;
* see /terms/house-prize and docs/20-identity-humanness-bots.md. The
* column is indexed so the leaderboard scope filter
* (humans|bots|all) can short-circuit cheaply.
*/
is_bot: 0 | 1;
}

export interface SessionRecord {
Expand Down Expand Up @@ -244,9 +253,34 @@ export class Storage {
this.db.exec(SCHEMA);
this.migrateUserTableIfNeeded();
this.migrateUserProfileColumns();
this.migrateUserBotColumn();
this.migratePhoneOtpTableIfNeeded();
}

/**
* v0.4 -> v0.5: add `is_bot` flag for the Open Bot Arena. The Phase 1
* launch (FIFA WC 2026) seeds ~18k bot rows so the public leaderboard
* is populated from minute one, and external operators register their
* own bots via the bot SDK. Bots are ineligible for the cash prize
* (humanness < 50 gate) so this column doubles as a fast filter on the
* leaderboard read path. See
* docs/superpowers/specs/2026-06-07-bot-arena-design.md §4.1.
*/
private migrateUserBotColumn(): void {
const cols = this.db
.prepare(`PRAGMA table_info(user)`)
.all() as Array<{ name: string }>;
const names = new Set(cols.map((c) => c.name));
if (!names.has('is_bot')) {
this.db.exec(
`ALTER TABLE user ADD COLUMN is_bot INTEGER NOT NULL DEFAULT 0`,
);
}
this.db.exec(
`CREATE INDEX IF NOT EXISTS idx_user_is_bot ON user(is_bot)`,
);
}

/**
* v0.3 → v0.4: add profile editor fields + HighLevel sync columns to
* `user`. SQLite ADD COLUMN is non-destructive; legacy rows simply
Expand Down Expand Up @@ -682,6 +716,7 @@ export class Storage {
favourite_team_code: null,
highlevel_contact_id: null,
highlevel_synced_at: null,
is_bot: 0,
};
this.db
.prepare(
Expand Down Expand Up @@ -726,6 +761,7 @@ export class Storage {
favourite_team_code: null,
highlevel_contact_id: null,
highlevel_synced_at: null,
is_bot: 0,
};
this.db
.prepare(
Expand Down Expand Up @@ -809,6 +845,7 @@ export class Storage {
favourite_team_code: null,
highlevel_contact_id: null,
highlevel_synced_at: null,
is_bot: 0,
};
this.db
.prepare(
Expand All @@ -819,6 +856,60 @@ export class Storage {
return rec;
}

/**
* Insert a synthetic bot row for the Open Bot Arena.
*
* Used by both the apps/seed-bots CLI (which mints the launch-day
* 18k seed cohort) and the bot SDK (which mints externally-operated
* bots on behalf of an API-key holder). Phone, email, telegram are
* all NULL by definition , bots authenticate via their owning API
* key, not via OTP.
*
* Idempotent on `id`: re-running the seed CLI does not duplicate
* rows or perturb existing bot brackets.
*
* Spec: docs/superpowers/specs/2026-06-07-bot-arena-design.md §4.1
*/
insertBotUser(opts: {
id: string;
display_name?: string | null;
country?: string | null;
favourite_team_code?: string | null;
created_at: number;
}): UserRecord {
const existing = this.getUser(opts.id);
if (existing) return existing;
const rec: UserRecord = {
id: opts.id,
phone: null,
display_name: opts.display_name ?? null,
country: opts.country ?? null,
telegram_id: null,
telegram_username: null,
created_at: opts.created_at,
last_seen_at: opts.created_at,
email: null,
first_name: null,
last_name: null,
city: null,
favourite_team_code: opts.favourite_team_code ?? null,
highlevel_contact_id: null,
highlevel_synced_at: null,
is_bot: 1,
};
this.db
.prepare(
`INSERT INTO user
(id, display_name, country, favourite_team_code,
created_at, last_seen_at, is_bot)
VALUES
(@id, @display_name, @country, @favourite_team_code,
@created_at, @last_seen_at, 1)`,
)
.run(rec);
return rec;
}

/**
* Link a Telegram identity onto an already-authenticated user.
*
Expand Down
55 changes: 55 additions & 0 deletions apps/auth-sms/test/storage-is-bot.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { Storage } from '../src/storage.js';

let s: Storage;
beforeEach(() => {
s = new Storage({ path: ':memory:' });
});
afterEach(() => s.close());

describe('UserRecord is_bot column', () => {
it('defaults is_bot to 0 for users created via findOrCreateUser', () => {
const u = s.findOrCreateUser('+6421000001', 100);
expect(u.is_bot).toBe(0);
const reloaded = s.getUser(u.id);
expect(reloaded?.is_bot).toBe(0);
});

it('defaults is_bot to 0 for users created via findOrCreateEmailUser', () => {
const u = s.findOrCreateEmailUser('dev@example.com', 100);
expect(u.is_bot).toBe(0);
});

it('defaults is_bot to 0 for telegram users', () => {
const u = s.findOrCreateTelegramUser({
telegramId: 12345,
telegramUsername: 'someone',
displayName: 'Some One',
phone: null,
now: 100,
});
expect(u.is_bot).toBe(0);
});

it('insertBotUser persists is_bot=1 and round-trips through getUser', () => {
const bot = s.insertBotUser({
id: 'bot_abc12345',
display_name: 'Carlos_BRA_42',
country: 'BR',
created_at: 100,
});
expect(bot.is_bot).toBe(1);
const reloaded = s.getUser('bot_abc12345');
expect(reloaded?.is_bot).toBe(1);
expect(reloaded?.id).toBe('bot_abc12345');
});

it('has an idx_user_is_bot index after migration', () => {
const rows = s.db
.prepare(
`SELECT name FROM sqlite_master WHERE type='index' AND name='idx_user_is_bot'`,
)
.all() as { name: string }[];
expect(rows.length).toBe(1);
});
});
43 changes: 43 additions & 0 deletions apps/billion-bot/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Tournamental Billion Bot - dashboard wrapper around @tournamental/bot-node.
# Self-contained: installs bot-node from a bundled tarball (v0.2.0 strategy
# recalibration) so the container does not depend on a live npm release.
# When 0.2.0 lands on the public npm registry, package.json can switch back
# to "@tournamental/bot-node": "^0.2.0".
FROM node:20-bookworm-slim

WORKDIR /app
ENV NODE_ENV=production
ENV DATA_DIR=/app/data
ENV DASH_PORT=4080
ENV DASH_HOST=0.0.0.0
ENV BOT_NODE_VERSION=0.2.0

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl python3 make g++ \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --create-home --uid 10001 botnode \
&& mkdir -p /app/data \
&& chown -R botnode:botnode /app

COPY --chown=botnode:botnode package.json /app/package.json
COPY --chown=botnode:botnode tournamental-bot-node-0.3.0.tgz /app/tournamental-bot-node-0.3.0.tgz

# Install bot-node CLI from the bundled tarball + better-sqlite3 locally so
# ESM imports resolve from /app. Expose the bot-node CLI on PATH via a
# symlink for convenience.
RUN cd /app \
&& npm install --omit=dev --no-audit --no-fund \
&& ln -s /app/node_modules/.bin/tournamental-bot-node /usr/local/bin/tournamental-bot-node \
&& npm cache clean --force \
&& chown -R botnode:botnode /app

COPY --chown=botnode:botnode dashboard.mjs /app/dashboard.mjs
COPY --chown=botnode:botnode fifa-wc-2026-fixtures.json /app/fifa-wc-2026-fixtures.json
ENV TOURNAMENTAL_MATCHES=/app/fifa-wc-2026-fixtures.json

USER botnode
EXPOSE 4080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -fsS http://127.0.0.1:4080/health || exit 1

ENTRYPOINT ["node", "/app/dashboard.mjs"]
Loading
Loading