All Markets

My Account

Open Positions

Loading…

Closed Positions

Loading…

Transaction History

Loading…

Submit a Question

Your Submissions

No submissions yet.

Question Voting

Gate-approved questions waiting for launch votes. Review the terms and vote to turn a question into a live market.

Loading…

Sources - Body

Sources - Notes

Source Registry

Who adjudicates, and what is true about them: mutability and its mechanism, the named-contributor dependency, permission records, and the exclusion reason where one applies. A market's matched rows are frozen onto it at convert - editing a row here never rewrites the terms of a market already trading.

Loading…

Fixtures

Who plays whom, which round, when - never results. The question gate reads this as its <schedule> block, so the closed-world existence check stops blocking real events. Every row is HUMAN-entered - the feed below only proposes. Names get a tick when they match the player lookup (aliases included, case-insensitive).

Loading…

Fixture Feed

The intake pipeline: the harvester detects fixture-relevant notices and lists them here with links. Follow the link, screenshot the schedule graphic, Propose from image (or paste a screenshot straight into the box), review, Add all.

Bulk add - one match per line: event | round | A vs B | 2026-08-17T10:00 (times UTC; append KST to auto-convert)

SOOP Harvest

Renders SOOP's notice board (a JS SPA) every few hours, archives notices before their ~2-month deletion, and DETECTS fixture-relevant posts - each becomes a linked row above awaiting the human graphic pass. It never writes fixture data. A failed render is a regression - the state line below says which state you're in.

Cast poll:

Parsed schedule lines land on the Fixtures leaf (with manual entry for what only eyes can read).

Stage Casts

Cast publication as a fact about a STAGE (held-questions release anchor). Stamp a stage when its English cast publishes; matching held questions show "ready to release" in Triage - the release itself stays a human decision.

Loading…

eloboard Scraper

Nightly sync of tracked players' career pages from eloboard.com (used with the operator's permission - non-commercial, a few reads per day in the KST night/morning window, sequential with a delay, attributed by site name). Downstream code reads OUR stored snapshots, never the live site. The run happens in the main loop; each KST day runs at most once, stalest players first.

Tracked Players

The scraper fetches ONLY the ids listed here - never discovers players by walking id ranges (that's the scraping behaviour the operator is fighting). The eloboard id is the wr_id in the player's page URL: board.php?bo_table=bj_list&wr_id=12.

Loading…

Site Images

Upload images and reference them from Content text as {img:tag} (currently rendered on the About page). Tags are lowercase letters/digits/dashes; re-uploading a tag replaces the image. {br} in Content text adds an extra vertical gap; {img:tag:indent} renders the image indented + narrower (so site screenshots read as illustrations); [label](#route) and [label](mailto:...) render as links. About/T&C sections run h1/p1h20/p20 (about.* / terms.*); the footer halves are footer.left / footer.right (multi-line). An EMPTY block hides that key's built-in default.

Loading…

Comment Policy

What the sweep model judges (conduct only) and what it must never flag (spoilers, truth, market talk, bare profanity). Version-stamped on every verdict.

Comment Evals

Loading…

Comment Reports

Open reports, oldest first. Spoiler reports may have already auto-hidden the comment (the fast path) - resolving here is the human decision.

Loading…

LLM Flags

The sweep runs behind the site (comments are visible on posting; it can retract). This is NOT an approval queue - clearing a false positive is the common case and takes one click. A cleared comment is never re-flagged.

Loading…

Comments - Browse

Loading…

Comment Bans

Loading…

Submission Bans

Banned accounts cannot submit questions (they can still trade). Manual add/remove only.

Currently banned

Loading\…

Order Book

Every bid on the selected market, matched portions split out. Partially matched bids show two rows: the matched slice and the still-open remainder.

Pick a market.

Hidden Markets

Hide old test markets from the front page. Nothing is deleted - evidence, scores and history survive, and a hidden market's page still opens by direct link. Resolvable as normal from Admin → Resolve.

Loading…

LLM Queue

Gate jobs waiting for (or receiving) an LLM answer, then recent completions with how long they took. Auto-refreshes every 5s while open.

Waiting / in flight

Loading…

Recently completed

Loading…

Localisation Keys

Admin Resolution

Pick a market from the dropdown above (or click a market on the Markets tab).

Accounts

Registered accounts (email-confirmed signups - the seeded "Acting as" identities aren't accounts). Approving creates the account's starting balance and emails them. All accounts are admin role for now (agreed 2026-07-12); role demotion is a later milestone.

Approvals live in Housework → Triage. Click a username to open its account view.

Loading…

Recent Events

Log types

Loading…

Back Office - Users

Loading…

Glossary - Body

Glossary - Notes

Policy - Body

Policy - Notes

Failure Dimensions

Dimensions - Notes

Prompt - Body

Prompt - Notes

Eval Questions

Loading…

Eval Questions - Notes

Submit Evals

Runs the eval set against each ticked model. Repeat >1 runs the ticked set that many times over - model output varies run to run, and repeats show by how much. Bare ids go direct to Anthropic; vendor/model ids route via OpenRouter (both lists: Admin → LLM → Models). Results land on the Scoring tab.

OpenRouter Models

One entry per line: vendor/model, optionally | provider/quant to pin the serving provider (becomes its own picker entry - model@provider/quant - with its own run history; fallbacks off, require_parameters on), optionally | Display name. A comma-separated list in field two (| deepinfra/fp8, parasail/fp8) makes a pool: OpenRouter routes within that allowlist by its own algorithm (fallbacks on inside the pool). The pool id sorts the providers, so reordering the line keeps its run history. Works for the gate too - pick the pool entry as the gate model.

Anthropic Models

One entry per line: model_id | Display | input | 5m cache write | 1h cache write | cache hit | output ($/MTok; the 1h write is informational - runs and the gate use the 5m tier). Legacy 6-cell lines without the 1h column still parse. Prices come from the Anthropic pricing page - Check catalog flags new/retired ids but the API exposes no prices, so keeping these current is a human job.

Models - Notes

Site Content

One [i18n.key] header per block; the text under it replaces that key's default wording sitewide (reload a tab to see it). Delete a block to fall back to the built-in default. The full key list lives under System → Keys. Text in ANY key supports {br}, {img:tag}, [label](#route) and blank-line paragraph breaks (the About/T&C commands), plus site variables: {gate_eta} renders the typical gate review time; {max_close_date} the latest market close the gate allows (today + its timing window).

Site Settings

Front Page

Tagged open markets take the featured-card slots ahead of the by-volume ranking (family legs are never featured - they'd misread as a market probability without their siblings).

Loading…

LLM Content Export / Import

Download the user-entered LLM sections as files - handy for backup or working on the prompt material outside the browser. Text sections download as .txt (version in the filename); eval questions and run history as .json; All is one JSON bundle of everything.

Import - same formats back in: a .txt replaces one text section (target auto-detected from the exported filename, or pick below); an eval-questions .json merges by question number (existing questions keep their run history) or replaces the whole set; the bundle .json imports its text sections + eval questions.

Site Pages Export

The user-facing copy from Admin → System → Content, as .txt. Per-page downloads are the [about.*] / [terms.*] / [privacy.*] blocks of that doc; All is the whole Content doc, footer and page copy included. Legal text worth keeping a dated copy of before editing.

Comment Moderation Export

The moderation policy as .txt (version in the filename); its eval questions and run history as .json. All is one JSON bundle, and includes the auto-hide dimension list and the configured sweep model - the settings that decide what a verdict DOES.

Production Gate

When ON, structured submissions run through the LLM gate (queue consumer in the main loop, concurrency-capped = bounded spend). Verdicts land in gate_verdicts; passes either auto-accept (gate changed nothing) or await the submitter's confirmation. When OFF, submissions auto-approve as before. The deterministic layer runs regardless.

Other Model Uses

Comment moderation: the retrospective sweep (never a gate - comments publish first, judged after), scored by its own eval set under Comments → Evals. Vision calls: every image read - frame classification, sweeps, and Propose-from-image on Fixtures. Blank model = inherit the gate model. Blank fallback = inherit the gate panel's fallback.

Comment moderation
Vision calls

Community Votes

Close votes: bidders on an OPEN market can vote to stop trading ("this is decided in reality"); it closes at this % of distinct bidders, rounded up. 0 = off. Votes to resolve: a CLOSED market resolves once one outcome collects this many distinct accounts' votes (strict plurality - ties wait for the admin). 0 = off (admin-only resolution - the recommended setting: any single account settling markets is dangerous).

Tester Limits & Notifications

Submission rate limits per role (N free per day, then at least M seconds apart), the daily LLM spend cap (gate pauses + eval runs refuse when reached; queued gate jobs wait, nothing is lost), and email alerts for warning/error log events.

Submissions - user role: admin role:
LLM spend cap:
Evidence capture: applies to sources marked rate-limited in LLM → Sources; a skipped capture never blocks a resolution
Market terms: applies at convert; existing markets keep their frozen dates
Machine reads: a missing proposal costs one read; a wrong one invites rubber-stamping
Frame classifier: labels are cached per (VOD, second) - markets sharing a VOD pay once
Comments: per-thread switches live on Admin → Comments
Email alerts:
0 recipients

QA Harness

QA mode:

Manual bid place a bid as a QA account
Auto-match seed matched volume around a price point (both sides, several prices)
Primitives:
Scenarios:
Click any button to see the response here.

Review Queue

Submissions the gate couldn't settle: self-consistency disagreements (the samples' verdicts shown side by side) and jobs that exhausted their retries. Accept puts the question back on the normal conveyor (converts next cycle, using the latest PASS contract if one exists); Reject closes it with feedback to the submitter.

Loading…

Resolutions

Loading\u2026

Account Approvals

Email-confirmed signups awaiting your approval. Approving creates the starting balance and emails them; account management stays in Back Office → Accounts.

Loading…

Recent Events

Admin-curated facts newer than the glossary (captured at resolve time; the gate carries active entries for 90 days). Shown here: drafts awaiting review, and entries expiring within 7 days or already expired - renew, edit, promote to glossary, or delete.

Loading…

Override Candidates

The override flywheel: every admin decision on the review queue is captured here with its reason and dimension. Harvest turns one into an eval question (expected verdict = your verdict, editable in the Evals editor) - human/gate disagreements are the highest-value eval cases there are. Retain defers 90 days; a candidate nobody harvests shows up here asking to be dismissed.

Loading…

Notices

Machine-detectable staleness: glossary not saved in 60+ days, model lists in 30+, and (when a resolution is overturned) recent-events entries whose evidential basis is gone.

Loading…

Failures

Rejected submissions: when, who, what, and how many attempts they made. Click a row for the verdict history; Promote sends it to the Review Queue for a human second look.

Loading…

Eval Runs

Loading…

Scoring - Notes

Project Status

Modules from the flowchart. Stripe colors: green = backend, red = frontend, violet = infrastructure (added since the flowchart). Status reflects the code as built, not the flowchart's (stale) annotations. Only major features noted. Test counts are from pytest; 1253 backend tests total, covering every meaningful backend module, plus a real-browser FE smoke harness (scripts/fe_smoke.py - seeds via QA, drives the player-facing pages headless, 11 checks incl. a live bid and comment through the actual click paths). Both run by CI on every push.

Backend Modules

a. Check Questions264 tests (harness+gate+freshness+sources)Full gate LIVE
Done: The whole gate pipeline exists. Deterministic layer (Phase 2): timing ≤92d, Unicode script scan, banned-terms fenced-block scan, title length, exact-duplicate guard, and the sequential-outright rules (a declared predecessor that is still trading rejects; the predecessor link exempts the successor from the lexical duplicate guard and nothing else) - instant 422s. LLM gate (Phase 3, behind Admin → Evals → Production gate toggle): gate-queue consumer (capped concurrency = bounded spend, retry/backoff/visibility-timeout), verdicts append-only in gate_verdicts, FAIL → feedback in the submitter's list, PASS auto-accepts when the gate changed nothing or awaits the submitter's confirmation on the proposal card; edits always re-gate. Accepted contracts (criteria, completed source/invalid, earlier trading close) applied to the market at convert. Full eval harness (question editor under LLM → Evals; LLM → Run for single + sequential batch runs across the whole catalog with per-model progress/estimates; Scoring tab; harness_version boundary - h6 = nonce'd submission envelope, h7 (2026-08-14) = group markets: the envelope carries market type / k / legs for family submissions, the date line is labelled "Market close:", and the schedule block says human-entered; borderline questions may accept a secondary failure dimension). Eval questions can describe family submissions (type/legs/k in the editor detail row, export/import, eval-set hash). Eval-iteration round 4 (2026-08-15): OpenRouter provider POOLS (comma list in the model-list line → sorted composite id; one provider = measurement pin, several = allowlist OpenRouter routes within; require_parameters on every call), runs with some-but-under-10% call errors re-run just those questions once (paced 30s for 429 recovery; 10%+ = lost cause, no retry), Run Batch appends to a running batch, and Scoring flags runs served by more than one provider.
Missing: Toggle stays OFF until the eval bar is met - otherwise all five gate phases are built (2026-07-13): Phase 4 self-consistency + review queue; Phase 5 override flywheel + submission fees (dormant at 0). Freshness layer built 2026-07-15 (harness v4): recent-events block injected into the gate prompt (admin-curated, 90d TTL, cited via evidence_entry_ids), URL-existence recovery path for verifiability rejections (Liquipedia allowlist, title-only, pre-live hold + admin confirm, false-negative fee refund), Housework → Triage leaf, eval fixtures for both (the harness never reads the live table). Source rework Phase 1 (2026-07-30, harness v5): exact-duplicate deterministic guard, notes/backstop/close-criteria/duplicate tool fields, Sources doc ({INJECT_SOURCES}, versioned src<N>), candidate-markets + tracked-player prompt sections (live in the gate, fixtures in evals), minute-granular trading close. Prompt-side additions + eval-set revision (1f) are Andrew's, pending.
c. Manage Market16 testsBuilt (basic)
Done: TWO community vote types (split 2026-07-15 per Andrew's actual intent). Close votes: bidders on an OPEN market vote to stop trading ("decided in reality"); closes at ceil(close_vote_threshold_percent % of distinct non-cancelled bidders) - skin-in-the-game holds at sweep time (cancel all your bids, forfeit your vote). Admin "Close trading now" overrule on Admin → Resolve. Group markets (Phase 3, 2026-08-15): close votes aggregate family-wide - bidders on ANY leg are the family's bidder set, one vote per account, the sweep closes every leg together; the vote panel lives on the family page. Resolution votes (2026-07-14, knob at 0 = OFF, the recommended setting): CLOSED markets resolve when one outcome collects N distinct accounts' votes as the strict plurality; ties wait for the admin; provisional/community_vote through the normal settle path.
Missing: Vote weighting/reputation (deliberately - same threat-model stance as launch votes), lifecycle beyond resolution (suspension etc.).
d. Bid Engine76 tests (incl. 20 property sweeps)Complete
Done: Contracts-based matching (2026-05-27 refactor - remaining_contracts is the source of truth, no stake↔contract drift). Price-time priority, partial fills, self-match prevention. Price-improvement residual released back to user at bid close via release_funds; non-BALANCE_004 release failures surface at WARNING (no silent stake retention). Sub-MIN_CONTRACT remainders close as closed_dust with residual refunded. Displayed market price is the order-book midpoint with a ¤1 volume floor; decoupled from the trade-tape fields. Money-conservation invariant (sum(user_totals) + sum(unresolved_match_pool)) covered by dedicated tests that bypass the per-test drift-repair fixture. Interface kept narrow (single process_bids entry point) so a future batch-auction engine can swap in. Price priority symmetric on both sides (NO-side inversion fixed 2026-07-06). Cancellation race hardened: consumes execute at match time and a failed consume VOIDS the match (counterparty compensated + re-reserved); status updates carry a WHERE guard so a concurrent cancel is never clobbered.
Missing: Platform fees on matches. The price-over-time chart's interpolation through quiet periods is a separate frontend concern noted in the bid-engine handover.
e. Bid Queue69 testsComplete
Done: Input validation, synchronous fund reservation, idempotency (3s TTL since 2026-08-14 - a double-click guard, not the old 300s lock on deliberately re-placing the same bid; the 409 message is player-readable both sides). Cancel-first fail-safe ordering (bid dies before funds release); pending-bid snapshot filtered to OPEN markets so bids queued just before a resolution can't match on the resolved market. The in-memory same-cycle guard only works in-process - cross-process safety is DB-level in the bid engine.
Missing: Complete for current scope.
f. Resolve Engine27 testsComplete
Done: Settles YES/NO/INVALID - winner payouts, loser & unmatched refunds, reservation release. Emits market_resolved events. Idempotent: re-running on a market that's already been processed is a no-op (matches query filters by linked bid status). closed_dust bids settle as 'settled' (economically matched; nothing refunded at settlement). Writes per-user financial results + delegates predictive scoring to the pluggable scoring package (Brier by limit price today).
Missing: Complete for current scope.
g. Main Loop45 testsComplete
Done: Orchestrates the full cycle; configurable interval, cycle-health warnings, daily summary, graceful shutdown. Commit-failure retry buffer + op-level dead-letter (a poison op no longer destroys the cycle's queued facts). Runtime money-invariant sweep (1/60s, deduped alerts) - caught a real stranded-dust bug on its first day live. Trading-close sweep flips markets past their deadline to 'closed' before each cycle's bid snapshot.
Missing: Complete for current scope.
h. Write to DB12 testsComplete
Done: In-memory operation queue batching every module's writes per cycle; supports $increment. Atomic drain() (take-and-clear) is the only safe committer pattern for the shared endpoint queue - closed a lost-write race between concurrent requests.
Missing: Complete.
j. Convert Question to Market14 testsComplete
Done: Turns approved questions into markets (new UUID, insert, question status → converted). Copies the structured contract onto the market: title → question, proposed source / invalid condition, resolution deadline (end-of-day) + trading_closes_at defaulting to it, plus the gate's earlier trading close, backstop and close criteria. Legacy prose questions still convert open-ended. Source registry (2026-08-04): the source TEXT is matched against the registry (slug / name / alias lines) into resolution_source_id / backstop_source_id / cast_channel_id + a defaulted read_mode, and the matched rows are FROZEN onto the market as source_registry_snapshot - resolve-time reads use the snapshot, so a registry edit never rewrites terms someone is already trading on. Sequential-outright lineage (predecessor_market_id) rides through. Group markets (2026-08-14): a partition/slate submission converts into a market_families row + one ordinary leg market per entry (composed question "«stem» - «label»", so every write-time title snapshot stays self-contained), with an automatic catch-all leg for open player sets (never for the closed race set); 18 group tests in test_group_markets.py.
Missing: Nothing outstanding. A registry MISS is deliberate behaviour, not a gap: the market converts with NULL FKs and the miss surfaces in LLM → Sources so listing is never blocked on data entry.
k. Commit to DB8 testsComplete
Done: Executes the write queue inside a transaction (asyncpg), rolls back on failure. Insert ops accept on_conflict/returning knobs; update ops accept an optional where guard (deferred updates can refuse to clobber a state that changed between queueing and commit).
Missing: Complete.
l. Notification Queue6 tests (shared)Built (basic)
Done: DB-backed (2026-07-14): queue_notification() inserts a notification_queue row from either process (API or mainloop); best-effort - a notification failure never breaks the source operation. Emitters: bid_engine (matches), resolve_engine (resolutions with per-user results), conversion (your market is live).
Missing: Priority-based batching, dedup, expiry - deliberately cut from the (over-specced) original spec until there's a customer for them.
Comments57 testsBuilt (dark)
Done: All three phases (2026-08-17, spec_comments.md). Threads on markets, families (one per family), and voting questions; queue → publisher → write_to_db pipeline (pending → visible per cycle); deterministic checks (links incl. obfuscations, envelope markup, all-non-Latin, length) + eligibility/rate/bans; everything stamps at POST. Votes (no per-user aggregate anywhere), reports with spoiler auto-hide + per-reporter caps, opt-in position badge (net side + VWAP, never size), per-submission submitter aliases, ban labels. LLM sweep: comment_mod_queue consumer in the mainloop, nonce'd envelope, strict parse (unparseable = failure), auto-hide routing, append-only comment_verdicts, policy doc + small eval harness. Admin → Comments group: Browse / Moderation (unified reports + flags) / Policy / Evals.
Missing: comments_enabled is OFF (flip in Settings when ready - Phase 2 ship gate is met); policy doc + 25-30 eval cases are Andrew's to author; T&C lines (retention, badge voluntariness, ban-label disclosure).
m. Send Notification6 testsBuilt (basic)
Done: Built 2026-07-14 - per-cycle post-commit drain of notification_queue into site_notifications (the bell inbox), transactional claim+deliver, keep-latest-100 prune per user. Emit points: bid matched (both parties), market resolved (each participant with their result), your market is live (submitter). FE bell with unread badge + panel.
Missing: Email/push channels (mailer.py is the email primitive when wanted), retry/dead-letter, per-user preferences.
n. Balance Queue15 testsComplete
Done: Queues reserve/release/consume/payout/refund ops with idempotency + duplicate detection; FIFO processing. 15 tests.
Missing: Complete.
p. Balance Management38 testsComplete
Done: Atomic balance ops holding the available + reserved = total invariant, with full audit trail. Audit-grade ledger: market_id + market_title denormalised onto every transaction row at write time. Consume dust (≤0.001 tolerance) returns to available instead of stranding in reserved - the bug the runtime invariant sweep caught live. All money writes pass Decimal, never float.
Missing: Play-money only - no deposit / withdrawal path (intentional for now).
q. Service Module0 testsNot built
Done: Nothing - no file exists.
Missing: Whole module. Intended as a caching layer (local + Redis + DB) to cut DB load. Not needed at current scale - everything reads Postgres directly.
r. Log Queue3 testsBasic
Done: In-memory FIFO queue at backend/app/modules/log_queue.py. event_log() enqueues; send_log drains once per main_loop cycle. Symmetric to Write to DB → Commit to DB but for log events. Local DB write stays synchronous inline in event_log() so the in-page panel sees events promptly.
Missing: Persistence (DB-table queue) for cross-process durability - currently lossy on shutdown. Acceptable for current scale.
s. Send Log5 testsBasic
Done: backend/app/modules/send_log.py drains log_queue and POSTs each item to AWKanban with a 3-second timeout. Silent no-op if AWKANBAN_URL/KEY unset. Failures aggregated to a single WARNING.
Missing: Retry with backoff, fallback channels, dead-letter table for unrecoverable failures. Best-effort delivery for now.
t. PAM (Player Account Management)24 testsComplete (lean+modular-scoring)
Done: pam.py with 5 read functions (account summary, open/closed positions, transactions, per-market leaderboard). Five endpoints under /api/users/{id}/... + /api/markets/{id}/leaderboard wired into the "My Account" top-tab and the specific-market detail page. ResolveEngine writes user_market_results on settlement, now including stake-weighted Brier loss + delta. Lifetime Brier averaged across resolved markets; per-market Brier shown in Closed Positions and on the resolved-market leaderboard.
Missing: Deposits / withdrawals, per-user limits, KYC. Real auth (separate track - Auth is "who you are", PAM is "what's true about your account"). The current "Acting as" picker treats users as a dev convenience. Pseudonymity (currently surfaces account names directly).
u. Back Office0 testsBasic
Done: Admin → Back Office tab - table of all users with creation date and balances, backed by existing /api/users. No dedicated backend file yet.
Missing: Admin actions (edit / adjust balances / ban). Read-only today. Will grow into the ops surface (audit-log views, market controls, system state).
v. Tooling (QA Harness)14 testsBuilt
Done: backend/app/qa.py + Admin → QA tab - seed primitives (incl. price-center selector and seed_family, 2026-08-15: a one-click [QA] partition with synthetic legs + crossing bid pairs), two scenarios, runtime loop-interval control, tag-by-prefix wipe (family rows included). QA mode is runtime-toggleable from the UI (overrides the QA_MODE_ENABLED env default). 14 tests focused on the dangerous + foundational paths (wipe safety, seed_users, seed_family, SeededRng determinism).
Missing: More scenarios (cancellation storm, partial fills) as the features they test mature.
w. Question Queue13 testsComplete
Done: Structured submission (gate redesign Phase 1): title + submitter + resolution deadline + optional source/invalid condition, with parent_question_id lineage for future resubmit-with-fix. Endpoint is dual-shape (legacy raw_text still accepted - QA harness). Full state-machine statuses in place (proposed / accepted / rejected / review / expired). (Letter w rather than m to disambiguate from Send Notification.)
Missing: Complete for current scope. (Validation lives in the gate - Phases 2–3; the accepted→convert conveyor also enforces the launch-votes threshold, 2026-07-13.)

Frontend Modules

1. Input Questionno harnessBuilt
Done: Submit Question tab - structured 4-field submission (title, resolution deadline, optional source + invalid conditions), instant deterministic-rejection feedback, and the Your Submissions list (auto-refreshing): status chips for the whole gate journey, rejection feedback with redirects, the gate's proposal card with Accept / Edit & resubmit, links to voting or the live market. Group markets (Phase 2, 2026-08-14 - open to every account, the gate's h7 envelope carries the family shape): market-type select + legs textarea + k input; the proposal card renders the family shape; converted group submissions link their #family page. Clear button (top right) empties every field and leaves edit mode - the escape hatch for an edit whose target became uneditable. Site-wide gate-off banner (admin viewers) flags the ungated submit path.
Missing: Per-user rate limiting on submission (gate Phase 5).
2. Display Questions for Voting10 tests (backend)Built (toggleable)
Done: Launch votes (2026-07-13): gate-accepted questions can require N distinct accounts' votes before converting (knob in Admin → Evals → Production gate; 0 = off = the 2026-07-09 decided flow, gate accept → immediately live - so QA stays clean). Voting tab is a sortable table (submitted / submitter / title / votes-by-distance-to-launch) with your own submissions starred, family option lists in the expanded terms, and expansion that survives the poller (2026-09-06); one permanent vote per account (who-voted stored per the threat model). Legacy/QA raw_text questions bypass, same as the gate.
Missing: Vote weighting / reputation (deliberately - threat model says distinct-account quorum first), notifications when a question you voted for launches.
3. Display Current Marketsno harnessBuilt
Done: Markets tab - four biggest-open market cards with price-over-time charts, plus the full market list. Group markets (2026-08-14): family legs collapse into one "group · N legs" row linking the #family/<id> page (one chart, one line per traded leg via the --series-1..6 theme tokens; Options table with inline per-leg bidding; family-wide close-vote panel). Families compete for featured-card slots as one unit; partitions carry the sum÷k badge (Phase 3, 2026-08-15 - suppressed unless every leg has a real price, never for slates). Admin front-page tag (2026-08-14): tagged open markets take the featured-card slots ahead of the by-volume ranking. Rows/cards are real anchors (browser history + ctrl-click) and carry "you have a bid here" dots (● matched / ○ open) from the viewer's positions.
Missing: Filter/sort controls (all/open/resolved, by volume/date). Deferred until the list grows.
4. Display Specific Marketno harnessBuilt
Done: Dedicated page at #markets/<id> (shareable/bookmarkable) - price-over-time chart, order book, Market terms block (resolution source / criteria / invalid conditions / both close dates), bid placement, resolution-state badge, post-resolution leaderboard, QA-only bid/match lists. Auto-refreshes. Non-open markets wear their state as a chip on the title, and resolved markets' charts render in the grey settled tokens. Family legs carry a "part of a group market" backlink and hide the community close/resolution vote panels (those sweeps skip families - close and resolve are family-wide acts). Bid-entry rework (2026-08-14): price typed in hundredths with the "0." in the label ("77" = 0.77), narrower boxes, amount/price reset when the viewed market changes; order-book rows carry ⇄ match (fill the form to take the whole level, opposite side at the complementary price) and ⧉ copy (join the level) - both only FILL the form, the side button stays the confirm.
Missing: Spread display (per-card note). (Resolution-voting UI landed 2026-07-14 with Manage Market.)
6. Admin Resolveno harnessBuilt
Done: Admin → Resolve sub-tab - select a market, resolve YES/NO/INVALID, settlement is queued. "Overdue for resolution" block (2026-07-12): open/closed markets past their resolution deadline, oldest first, click-to-select; the daily summary carries an overdue count too. "Newsworthy?" capture box after each resolve (2026-07-15) - one click drafts a recent-events entry for the gate's freshness block; Skip costs nothing. Group markets (2026-08-14): selecting a family leg swaps the single-market buttons for the family block - tick the YES legs (radio at k=1) and the whole family resolves atomically via /api/admin/resolve_family, which asserts cardinality BEFORE anything moves. Front-page toggle (2026-08-14) on open non-leg markets: tagged markets take the featured-card slots ahead of the by-volume ranking.
Missing: Vote-threshold-driven resolution queue (gated on Manage Market).
7. My Accountno harnessBuilt (basic)
Done: Header card (balance + lifetime stats), open + closed positions, paged transaction history with market-title links, resolved-market leaderboard, currency formatting helper. Reads from PAM (t.). Notifications bell (2026-07-14, panel chrome 2026-08-13, polish 2026-08-14): the list scrolls under a fixed header, clicking a notification marks it read, bid_matched messages name the market, and the header balance refreshes on the same 30s interval (it went stale between boots while matches settled in the mainloop process).
Missing: Deposit/withdrawal UX (real-money era).
8. Accounts22 tests (backend)Built (basic)
Done: Registration (username/email/password) → confirmation email → admin approval queue (Housework → Triage since 2026-07-20; registration toggle in Settings) → sign-in sessions (30-day token; legacy API key still works) → password reset via one-time email link. Admins can open any account's My Account view from Back Office → Accounts. Passwords bcrypt-hashed, irretrievable; email/reset tokens hashed + single-use; per-IP rate limits on the two email-sending endpoints. All accounts admin role for now.
Also done (caveat resolved 2026-07-12): Identity enforcement - role='user' sessions can only act as themselves (bids, questions, submissions, PAM), with the Admin tab hidden and the Acting-as picker pinned. Role toggle per account in Admin → Accounts; admin sessions + the API key keep acting-as for QA. Since 2026-07-13 the Acting-as picker only shows in QA mode (signed-in users act as themselves) and the seeded 'admin' picker identity is retired - admin is a role, not a user.
Missing: Profile page, email change, session list/revoke UI, rate limits on question submission.

Infrastructure (cross-cutting, not on the module flowchart)

Deploymentno harnessLive + CI
Done: Running at prediction.anw.ee - Docker-compose stack on Hetzner, fronted by Caddy with Let's Encrypt TLS. CI: GitHub Actions runs the full pytest suite against a from-scratch database on every push (which also proves setup_db.sql works on fresh installs). Deploy ritual: git pull → run any new deploy/migrations/*.sqldocker compose up -d --build. Boot-time schema guard emits a red schema_behind_code event naming any missing columns if a migration was skipped.
Missing: CD (auto-deploy on green), off-box backup replication (nightly on-box pg_dump with 14-day rotation exists since 2026-07-09), log rotation.
Authentication22 testsBuilt (sessions + legacy key)
Done: Per-user sessions (bcrypt passwords, hashed 30-day tokens, X-Session-Token) alongside the legacy shared API key (X-API-Key - full admin, kept for QA/scripts). Middleware accepts either; /api/admin/* + /api/qa/* additionally require an admin credential. See FE module 8 for the account lifecycle.
Missing: Credential rotation story, session revoke UI. (Identity enforcement landed 2026-07-12 - non-admin sessions act only as themselves.)
Source Registry + Resolution Evidence256 tests (registry+evidence+phases 3–4+classifier)Built (Phases 2–4 complete)
Done: "eloboard" is a thing, not a string. The sources table records mutability and its mechanism, the named-contributor dependency, permission records, known instability, the per-source settling period, the default read mode, and the exclusion reason where one applies - Liquipedia and Namu Wiki are seeded excluded WITH their reasons so that fact never depends on data entry, and eloboard is seeded rate_limited because evidence capture reads that flag. Editable at LLM → Sources, beneath the prose doc the LLM reads (one leaf, two layers). Matching is against slug / name / alias lines; the matched rows are frozen per-market at convert. Player identity is folded into tracked_players (Hangul, romanisations, date-ranged aliases, race, RepMastered name, disambiguation flag) rather than grown as a parallel table. Resolution evidence: resolving takes an optional resolution URL / evidence URL / note; we fetch and archive OUR OWN copy (the archive is the artifact, the URL is a convenience pointer - eloboard's URL churn is confirmed) into an append-only resolution_evidence row (RESTRICT, like gate_verdicts). Capture is best-effort and never blocks settlement; failures surface in Triage. Captures against a rate_limited source are gated by evidence_capture_min_gap_seconds and skipped, not delayed, sharing the scraper's fetch clock - so resolving twenty markets can't spend twenty eloboard reads. Public Resolution Evidence panel on resolved markets: outcome, the sources as FROZEN at listing, resolver, hash, links, and the archived copy (served as text/plain - third-party HTML must not run on our origin).
Cast evidence floor (Phase 3a, 2026-08-05): ASL match markets turned out to be SINGLE-SOURCE - eloboard carries no ASL results this season, so the English cast is the sole resolver with no live backstop, and a VOD can be pulled or region-locked overnight. Capture is therefore redundant across ARTIFACT KINDS since it can't be redundant across sources: the transcript (what was said, archived to source_snapshots and reused rather than re-fetched), the overlay frame (what was shown - the identity read the transcript can't give), and a timestamped VOD URL. Transcript search is a TIME-INDEX and a candidate generator only; the admin looks at a frame and decides. Three modes, none an error: candidates found, captions-present-but-nothing-matched, and no-captions - the last two are manual mode, which is the expected path until the phrase list widens. Heavy work runs in an out-of-band browser_jobs queue (mainloop process, never the resolve request), so a multi-minute VOD download can't block settlement.
Resolver tooling (Phase 3b, 2026-08-05): the state machine was never the cost - a human opening a Korean board or a six-hour VOD in another tab is. The resolve panel now shows what we already store: the FROZEN source contract (not the registry as it reads today), source health at the point of use, matched tracked players with their latest stored eloboard values, and one-click preview of a candidate page (fetched AND archived - a fetch we've paid for is a snapshot we keep; parsed values render instead of a wall of Korean HTML, which is why a parsed source needs no screenshot). Nothing pre-fills the outcome - a resolution hallucination moves money and is only caught if someone disputes it. Single-source markets say so explicitly and state the real fallback (no cast by the deadline → INVALID) rather than rendering an empty backstop slot that reads as "to be determined". resolution_flags capture "something is off" (conflict / unparsed / other) - they carry the admin's note and never a result, so the queue can't spoil its reader; an open conflict pauses auto-finalize; resolving CLEARS rather than deletes, because a resolution that had a conflict flag is what a dispute wants to find. Also closes a collision the preview flow created: capture reuses a copy of the same URL fetched within the hour, so preview-then-resolve no longer records a rate-limit error despite a fresh archive sitting right there.
Readability (Phase 3e, 2026-08-05): the answer differs by source kind, and the obvious framing is backwards. For a source we PARSE, the extracted values are the readable evidence - "eloboard, fetched at this time: 4-2 recorded" is checkable by the English-speaking disputer this is all for, where a screenshot of a Korean page is not - so no picture is taken, which also keeps Chromium off the metered source's path entirely. Sources we DON'T parse (casts, SOOP, news) get a rendered PNG attached to the same snapshot row, queued out-of-band to the browser worker so nothing blocks settlement. Served as image/png (inert - none of the text/plain reasoning applies). Degrading is a less readable archive, never a lost one.
Frame classifier + remote media (4b Plan B / Option D, 2026-08-20): the reader that survived two review rounds labels frames instead of proposing results - one vision question per frame (mid-game / score graphic / victory screen / production card / other + a verbatim match-label transcription), so the resolver panel takes the admin straight to the decisive picture and the human reads the number. Labels are cached per (VOD, second) - markets sharing a group VOD pay once - budget-capped, and skipped entirely at the daily LLM cap (resolver tooling must never pause the gate). A whole-VOD sweep (a frame every 2 minutes, decisive kinds returned only) rescues manual mode, where the cost was always FINDING the frame. Measured by a frozen 17-frame corpus harvested from the live ASL 22 broadcast (graded/hard split, decisive-kind recall reported separately; scripts/frame_eval.py, run on gate-model change). Commit 0's answer forced Option D: YouTube bot-checks datacenter IPs, so yt-dlp work (frames, sweeps, transcripts, the evidence frame) runs on an ADMIN MACHINE via scripts/media_worker.py - polls the media-job queue, executes locally, posts back; the server classifies on ingest. Circumvention tooling was rejected on audit-surface grounds (the conduct note in CLAUDE.md). A fully manual path exists regardless: attach a screenshot of the decisive moment at resolve and it becomes the evidence frame with no yt-dlp anywhere. Candidate moments carry transcript context (click-expand); Check Now names WHY nothing was found.
SOOP harvest (Phase 3f, 2026-08-05): SOOP publishes official results only after the group stage, so it can never resolve anything - by the time a notice exists the cast settled the market weeks earlier. Its value is an independent organiser record captured before its ~2-month deletion clock runs out, attached as evidence_kind='supplementary' with NO outcome (it corroborates, it never asserts) and never flipping a market - notices explicitly say 일정 변동이 있을 수 있습니다. A notice that CONTRADICTS the cast raises a conflict flag for a human; ambiguous matches escalate rather than guess. Deletion is a hard-fail: soop_capture_at_risk fires on both routes to permanent loss - a missing expected notice AND an exhausted render job (the likelier one, and the one that would otherwise fail silently at the layer nobody watches). Distinct urgency from source_unhealthy on purpose: "down" is recoverable, "will be deleted before we capture it" has a closing window.
Phase 4 automation layer (2026-08-10): machine reads fill REFERENCE fields and raise FLAGS; a human picks every outcome. Fixture pipeline (reworked 2026-08-12): every fixture row is HUMAN-entered - the harvester renders the board every 8h and DETECTS fixture-relevant notices (linked to-do rows + Triage alert + ops email; auto-ingest of parsed lines retired as unchecked noise). The graphic pass: follow the link, screenshot, vision-proposed bulk lines (image paste supported; event tag applied deterministically), review, Add all - with live per-name roster ticks (case-insensitive, aliases included) and a Triage notice for unresolved names. Rows ride the gate's <schedule> block (never results; empty feed omits the block). Machine reads: eloboard-routed stat markets get a proposed outcome from stored snapshots via narrow claim readers; every uncertainty (ambiguous threshold, straddle, stale, two players) resolves to NO proposal with its reason - a wrong number invites rubber-stamping. Cast poll: uploads-feed polling detects a match's VOD (handle/hangul/alias matching), auto-runs the transcript analysis, honours cast precedence with a preferred-wait window, supersedes re-uploads, and offers per-market Check Now. Spoiler property pinned throughout: proposals and candidates are admin-panel data only.
Missing: Registry content is Andrew's data entry (contacts, permission prose, per-claim-class settling hours) - the seed is skeleton rows. Overlay OCR + identity-table resolution + the dual on-frame check - the ONE Phase 4 piece still gated: it needs a real ASL 22 broadcast frame as a fixture before its crop coordinates are anything but guesses on a settlement path. source-health sweep and the auto-finalize timer are built (Phase 3c/3d); a structured claim_classes table and machine parsed_value reads stay deferred.
eloboard Scraper21 testsv1 (player pages)
Done: Nightly player-page sync from eloboard.com under the operator's written permission (non-commercial, few reads/day in a KST night/morning window, sequential + delayed, attributed by name - conditions recorded in CLAUDE.md). Fetches ONLY admin-listed tracked_players ids (no enumeration); raw HTML + parsed stats stored per fetch in source_snapshots (the evidence-capture artifact); label-anchored Korean parser (total/ELO/races/tournaments) verified against a live page; runs in the main loop, stalest-first rotation, once per KST day; control panel at LLM → Players (leaf renamed from Scraper 2026-08-06 - we scrape in several places now; what matters is what this one is for); parse/staleness failures surface in Triage. Player identity (2026-08-04): a successful scrape records the player's real name from the 플레이어 cell into tracked_players.hangul, filling only what is empty so hand-reconciled values always win. Names are stored verbatim and never transliterated - Korean personal-name romanisation is conventional, not phonetic, so a generated spelling would be plausible and wrong. A Latin page name is surfaced in the roster (page_name) rather than stored, because tag stripping means the parser captures a single token.
Missing: ASL result board (fast follow - enumerate pages, title grammar, settling period), snapshot-backed displays on market pages (with the eloboard attribution), romanisations stays a human field (the reliable machine path is a source that renders 김민철 SoulKey side by side, not transliteration), unified rate config (evidence capture gets its own per-host gap knob in source rework Phase 2; merge with the scraper's window/delay limits later - both spend the same read budget).
Design System (theming)6 tests (serving)Phases 0–4 + responsive
Done: Two-file split: theme-broodwar.css (per-niche - fonts + ink variables; copy this one file to re-skin a niche) + design-system.css (shared - scale, 5+2 type roles, element defaults). Cache-busted serving (?v= mtime) with a dev-only ?theme= preview override. Tabular numerals on money/number tables. Phase 2: full color vocabulary (inks, surfaces, borders, yes/no market sides, ok/warn/danger/info semantics, admin/QA accents); charts read the theme via themeVar(). Phase 3: the whole inline-style long tail swept onto var() - ZERO hex anywhere outside the theme file (census-enforced) + element-class kit. Phase 4: the dark Broodwar theme is live - sketch ink palette, authored dark surfaces, self-hosted Chakra Petch (OFL, latin+latin-ext woff2), themed form controls, Chart.js theme defaults. Light palette kept as theme-default.css (/?theme=default). Responsive pass 1 (2026-08-16): player-facing surfaces at phone widths (two-row nav with scrollable tab strip, stacked cards/bid buttons, two-block family Options + Open Positions rows, clickable transaction history) - admin stays desktop-only on purpose. Settled-chart tokens (--settled/--settled-soft): resolved markets' lines go grey. Playwright screenshot rig (scripts/screenshot_pages.py): every player page at every target device size in one run, local or prod (--api-key, --user).
Missing: Dark-theme VALUE iteration is Andrew's design pass (everything is one file: theme-broodwar.css). 563 inline style= attrs still convert to system classes opportunistically. Niche identity - name/favicon/banner copy/chart series palette (Phase 5). Plan: .claude/plans/plan_frontend_design_system.md.
Site Footer / Legalno harnessPlaceholder
Done: Minimal footer (contact, design/architecture + code credits) in the tab-row visual language.
Missing: Actual legal requirements before opening - imprint? gambling-jurisdiction notices? privacy/GDPR page? Terms? Deliberately a placeholder until that's figured out.
Localisation (LOC keys)no harnessEN keys, partial coverage
Done: I18N_EN dict (? keys, last updated ? - periodically ask: do we still need them all?) + t(key) for JS strings + data-i18n/-title/-placeholder bindings applied at boot. Missing keys render as the raw key (self-reporting). Browsable table under System → Keys. Covered: nav, auth overlay, user bar, all four user tabs, admin headers/nav, Refresh buttons.
Missing: Long admin intro paragraphs, tooltips, and JS-built status messages still carry EN literals - migrate incrementally via the same mechanism. Only EN exists; a second language means a second dict + a picker.

About

Terms & Conditions

Privacy Policy

AI Models In Use

Loading...