← Back to the project
For engineers: data model, states, rules

UNRIGGED: BUILD DOCUMENT

2 August 2026. The developer's document. Tables, schemas and enums. Synced to the 2 Aug spec revision: phase-one community layer, 4.7 evidence ladder, and the founding-corpus seeding model.

This document is the how. The specification is the what and the why. Where a rule needs a reason, this document points to a chapter in the specification (written spec 4.3) rather than repeating it. Where a launch number is needed, it is here. Where this document and the specification prose disagree, this document and the specification's chapter 14 contract win together, and chapter 14 wins over this document.

Build order and the architecture principles are in spec 12.


0. THE SLICE

A slice is one casino, one payment method, one net-position bucket, one amount band, one VIP flag. Every threshold, median, count, cap and trend is per slice (spec 14.1). The 5-distinct-member gate lives in spec 14.2.

slice_key = (casino_id, method, net_position, amount_band, vip_at_time)

Amount band and VIP are part of the key but open progressively: a figure shows only once its full slice clears the gate, and coarser splits (method, net position) show first while finer ones fill.

The Overdue benchmark is the one exception: computed on (casino_id, method) only, so a live record can be judged before its finest slice fills.


1. ENTITIES

The store is organised around entities, not tasks. An entity gets its own table if you can ask a useful question of it without joining to something else (spec 12).

Entity Owns Children
Member A person. Auth, verification tier, level. Account links, sessions
Casino A brand. Provisional or confirmed. Passport fields, terms snapshots, records
Operator The company behind one or more casinos. Casino claims
Record One logged withdrawal. Payment legs, evidence, endings, written account, flags
Ledger Tester payments. Append-only. Payout requests
Comment A community post or reply. Screened like any player text. Never touches a number. Votes
Vote One up/down per member per comment. Verified-player only.

Casino and Operator are separate tables, joined by a dated claim. The measurement lives on the brand and must never touch a commercial account (spec 6).


2. THE RECORD STATE MACHINE

2.1 States

Exactly one state per record, persisted. Payment states (WAITING, PART_PAID, PAID) are recomputed from the legs when a leg changes. The other states are set by their transition event or by the sweep in 12b, and are stored, never re-derived from legs.

State Live/Terminal Counter Counts as unpaid In payout median In denominator
DRAFT Live none no no no
WAITING Live Still waiting yes no no
PART_PAID Live Part paid yes, outstanding part only when complete when complete
PAID Terminal Paid no yes yes
METHOD_CHANGED Terminal Method changed no no method median yes
REFUSED Terminal Refused no, counted separately no yes
CANCELLED Terminal Cancelled no no yes
ACCOUNT_CLOSED Terminal Account closed yes, separate no yes
CLOSED_SHORT Terminal Closed short yes, refused part no yes
ABANDONED_UNPAID Terminal Gave up waiting yes no yes
UNCONFIRMED Terminal Unconfirmed no no yes

Every record sits in exactly one counter. PART_PAID is never split between Paid and an outstanding bucket (spec 4.1).

2.2 Flags, not states

Flags sit on top of the state. disputed and removed are the two editorial flags. overdue is a third boolean, but it is sweep-computed from the clock, not set by a person (spec 4.1).

Flag Effect on state Effect on numbers
overdue none, it is a property of a live record the record shows as overdue; sets ever_overdue once, which the silence rules read
disputed none none, the flag is shown
removed none, state retained underneath drops out of every count and median

Overdue is a property, not a state. Any live record (WAITING or PART_PAID) whose elapsed time passes 1.5x its benchmark carries overdue = true, computed each sweep. It never competes with PART_PAID. ever_overdue latches true the first time and is what distinguishes Abandoned from Unconfirmed on silence.

2.3 Transitions

From To Trigger Guard
DRAFT WAITING player confirms details evidence present or fields typed
WAITING PART_PAID a leg arrives, balance still positive
WAITING/PART_PAID PAID player confirms arrival, balance zero
PART_PAID CLOSED_SHORT casino refuses the remainder refusal artifact
PAID WAITING payment bounced or clawed back after being marked paid reversal leg recorded
WAITING REFUSED casino declines before any leg, artifact recorded refusal artifact. A PART_PAID refusal goes to CLOSED_SHORT.
any live CANCELLED player reverses the withdrawal
any live ACCOUNT_CLOSED player locked out, withdrawal outstanding own outcome only if evidence artifact attached, member verified, and link predates the withdrawal; otherwise the state sets but the record is buried and excluded from aggregates (spec 4.8)
any live METHOD_CHANGED balance reaches zero via a leg whose method differs from the requested method derivation compares leg method to requested method; same method to zero is PAID
WAITING/PART_PAID UNCONFIRMED 21d silence and ever_overdue = false and never said outstanding
WAITING/PART_PAID ABANDONED_UNPAID 21d silence and (ever_overdue = true or said outstanding)
REFUSED PAID escalation, casino pays payment artifact

Overdue is not a transition. The sweep sets overdue and latches ever_overdue on any live record past 1.5x its benchmark (§12b).

Silence transitions use "last contact" = last player reply, not last reminder (§7.3).

2.4 Side effects, transactional with the transition

On publish, remove, dispute-clear and correct: recompute the affected counters, the affected medians, the member's tier, and the not-paid line. All of it lands or none of it does (spec 12). Removal by direct action and removal by upheld flag produce identical effects.

2.5 Other entity transitions

Same format as the record machine. Every transition writes the audit row from section 6.

Casino (status)

From To Trigger Guard
none provisional first record names a brand no fuzzy match resolves no confident match
none listed staff seed a known brand pre-launch passport fields present
provisional listed person confirms via the provisional queue link.verify; real operator, not a mirror, licence plausible, domain matches; page indexable
listed suspended staff freeze under a legal demand or fictitious brand link.verify; reason set, aggregates frozen. The page renders a visible suspension notice, never a silent hide (spec 11.10).
suspended listed staff reverse it reversible

A provisional confirmed as a mirror is merged not transitioned: records move, old URL returns 410. A rejected provisional keeps its row, noindex, never hard-deleted.

Dispute (outcome)

From To Trigger Guard
none open POST /operator/dispute domain-verified operator; min claim length; attempt <= 2; open disputes < 5; throttle clear
open correction_applied a factual error is fixed flag.adjudicate; fact-fix never a ruling; recompute per 2.4
open no_correction no factual error shown flag.adjudicate; 72h target

The disputed flag is independent of the outcome and moves no number.

Operator reply (moderation state)

From To Trigger Guard
none submitted POST /operator/reply domain-verified operator; no existing reply (409); throttled
submitted screened automatic screen runs before publication, no manual step
screened published screen raises no flag publishes in seconds
screened removed flag upheld by a person record.review; author told; visible marker
published removed removal rule matched record.review; visible marker, never silent

No transition here touches a record state or number.

Account link

From To Trigger Guard
none pending POST /record/{id}/link with proof asked after the first record
pending linked name matches, or no-KYC casino has no name proof_tier set by the evidence
pending mismatch_held name on proof differs 202, queued
mismatch_held linked person resolves it, once link.verify; 48h target

On linked the member becomes distinct for every gate and the record leaves the unlinked tier.

Payout (payout_state)

From To Trigger Guard
none requested member requests a payout verified tester; amount >= minimum; <= tier max; <= balance
requested review compatibility checks pass coin, network, address compatible
requested denied incompatible hard reject, no person
review approved person approves payout.approve; balance re-checked; ledger debited now
review denied person denies with reason payout.approve; reason in audit row
approved paid crypto sent tx_hash recorded at this step, not at approval. Approval records intent and debits the ledger; paid records the completed transfer.

paid and denied are terminal. A denied request never touched the ledger.


3. FIELD DICTIONARIES

3.1 Record

Field Type Notes
id uuid
member_id uuid
casino_id uuid may be a provisional casino
account_link_id uuid, nullable null until linked, see §5
state enum §2.1
disputed bool §2.2
removed bool §2.2
removal_reason, removed_at string, ts, nullable visible marker, append-only public log (spec 11.10)
method enum §8 read from screenshot, confirmed
chain enum, nullable crypto only
amount_original decimal
currency iso 4217
amount_usd decimal converted at request-date rate (§9)
amount_band enum §8 derived from amount_usd
net_position enum: winnings / own_money / unknown the net-position bucket, the 3.4 answer, part of the slice key (spec 3.4)
net_position_source enum: verified / stated / unknown how we know it: verified from history, player-stated, or unknown (spec 3.5)
vip_at_time bool, self-reported
requested_at timestamp
last_player_reply timestamp drives the silence clock; seeded from requested_at at creation, updated on every reply including confirm-details
last_reminder_at timestamp
ever_overdue bool set once, gates Abandoned vs Unconfirmed
said_outstanding bool player said still waiting at least once
published_at timestamp, nullable null while DRAFT
closed_at timestamp, nullable drives 90-day image destruction
benchmark_used enum: casino_method / sitewide_method / published_window / none which source applied
benchmark_value_minutes int, nullable the actual value applied at the time, frozen so a later median change never rewrites this record's overdue status
evidence_tier enum §8 bad-ending ladder only (spec 4.7)
arrival_proof enum: bank_or_wallet_arrival / on_chain / none paid records only, separate from evidence_tier, never a Tier number (spec 7.1)
is_tester bool separate bucket, never fills an organic gate
is_control bool withheld until resolved (§7.5)
acquisition_source enum: organic / shared_link / operator_prompt / community anti-selection signal (spec 9)

3.2 Payment leg

Field Type Notes
id, record_id uuid
amount decimal negative for a reversal
method enum §8
arrived_at timestamp
reversed bool

Outstanding balance = requested amount minus sum of leg amounts. state derives from it (spec 4.5). The payout median uses the final positive leg's timestamp.

3.3 Account link

Field Type Notes
id, member_id, casino_id uuid
proof_tier enum: registered / deposited / withdrew spec 5
name_match enum: matched / mismatch_pending / no_name no-KYC casinos have no name
created_at timestamp

3.4a Member

Field Type Notes
id uuid
email string the only required field at first record
identity_verified bool KYC through provider, token only, no documents
points int sum of point_events, cached
level int 1-99 derived
tier enum: registered/verified/established/senior/top derived from points plus gates (spec 7.1)
false_statements int gates Senior
last_evidenced_at timestamp drives the senior-privilege pulse

3.4b Casino

Field Type Notes
id uuid
name, primary_domain string
status enum: provisional / listed / suspended provisional is noindex (§10)
claimed_by uuid, nullable current operator, denormalised from the latest claim row
claim_history table dated claims (operator_id, claimed_at), so ownership history survives a change
passport json licence, entity, ownership, sibling brands, one dated terms field (spec 6.1, spec 6.2)
suspended_reason, suspended_at string, ts, nullable freeze-and-hide, aggregates frozen, reversible

3.4c Operator

Field Type Notes
id uuid
legal_name string
verified_domain string, nullable domain-control magic link (spec 11.1b)
paid_profile bool presentation only, never read by any calculation
profile_fields json operator-stated, rendered beside sourced fields, never overwriting them

3.4d Dispute (a real entity, not a boolean)

Field Type Notes
id, record_id, operator_id uuid
claim_text string min length enforced
evidence_uri string, nullable
attempt int max 2 per record (spec 14.12)
outcome enum: open / correction_applied / no_correction correction is a fact-fix, never a ruling (spec 6)
resolved_by, resolved_at uuid, ts

3.4e Operator reply

Field Type Notes
id, record_id, operator_id uuid
body text append-only, screened like player text
created_at timestamp one per record, throttled

Replies annotate, never change a state or a number (spec 11.1b).

3.4f Ledger and payout (tester payments)

Field Type Notes
id, member_id uuid
type enum: test_earned / withdrawal append-only, balance = sum
amount decimal
payout_state enum: requested / review / approved / paid / denied separate payout.approve permission
tx_hash string, nullable required on every approved crypto payout
dest_address, coin, network string validated for compatibility, wrong network is a hard reject

3.4f2 Reward and payout guards

Rule Value
Earned per approved contribution Paid through the public loyalty engine (same as every player, no closed batch, no pay-per-verdict): ~$8 loyalty row, plus 20 points
Evidence bonus +$2 loyalty on any approved contribution backed by evidence (account history or transaction files). Pays for the verification work, never the verdict. Applies to everyone (spec 7.2)
Available balance sum of ledger rows
Minimum payout $20 (tune via config.thresholds)
Tier maximum per payout Verified $250 / Established $500 / Senior $1,000 / Top $2,500
Ledger debit one negative withdrawal row at approval, never at request
Compatibility coin / network / address validated at request, wrong network hard rejects
tx_hash required on every approved crypto payout before paid
Permission payout.approve only, separate from content permissions

3.4f3 Seeding: the founding corpus (spec 7.2)

The launch corpus is roughly 2,000 measured withdrawals from founding members: veteran real players (for example experienced reviewers from the public paid-review contests) logging casinos where they already have real accounts and history. Same log-and-write flow as any player, same quality approval (positive or negative is irrelevant), paid through the same public loyalty engine as everyone, plus the evidence bonus. No closed batch, no pay-per-verdict.

Founding records stay in their own separate bucket, labelled, never merged into organic figures, and never fill an organic gate (spec 7.2 / 3.12). Legal posture: incentivized-and-disclosed contributions from genuine users, not fabricated reviews from non-users; disclosure is intrinsic because the loyalty engine is a public feature applied to every contribution.

3.4g Attachment visibility

Per file, server-enforced on every read. Default masked.

Viewer Sees
public masked version if published, else nothing
author their own masked and original
staff with evidence.unmask unmasked original, logged
operator nothing, ever

3.5 Evidence

Field Type Notes
id, record_id uuid
masked_uri string what persists
original_uri string, nullable destroyed 90d after record close (§7.4)
ocr_confidence float auto-fill at >= 0.85, else typed
scanned bool malware scan before storage

3.6 The written account

Testimony beside a record, never a measurement. Standalone accounts touch no number (spec 5).

Field Type Notes
id uuid
record_id uuid, nullable null exactly when standalone
member_id uuid
body text free text, never a dropdown, grammar untouched
is_standalone bool
moderation_state enum: published / flagged_held / redacted / removed
created_at timestamp

Updates are child rows, never edits. The author never rewrites published text. Moderation is our action, never silent.

Field Type Notes
id, account_id uuid
body text screened like the parent, dated thread
created_at timestamp

Ending questions, asked at every terminal state:

Field Type Notes
deposit_again enum: yes / no, nullable never on standalone; one per member per casino, latest wins; published as a range behind the 5-member gate, no 8-record floor
documents_asked enum: no / once / more_than_once every ending

Standalone deposit-proof tier, from the account link proof_tier:

Tier Proof Lands
registered account page screenshot buried bucket
deposited transaction history showing a deposit main standalone section
withdrew full evidenced withdrawal attached, feeds the numbers

Routes: POST /record/{id}/account (attached), POST /casino/{id}/account (standalone, tier guard), POST /account/{id}/append (author only).


4. ENUMS


5. ACCOUNT LINKING

  1. First record is filed with no link (spec 2.3). account_link_id is null. Record publishes at the unlinked tier, feeds no median or counter.
  2. Link is requested at the first reminder. Proof is a screenshot of the casino account page.
  3. deposited tier needs the transaction history to show a completed deposit. withdrew needs a full evidenced withdrawal.
  4. Name mismatch goes to the link.verify queue, resolved by a person once. No-KYC casinos have no name to match and link at the lower tier.
  5. OPEN: relinking after a username change, more than one account at one casino, revocation, operator ownership change.

5b. COMMUNITY LAYER (comments and votes, spec 15)

Added in the 2 August spec revision: comments and up/down voting are phase one. Two entities, one hard guard, one hard invariant.


6. PERMISSIONS

Grantable units. Roles are bundles. One person holds all at launch; the audit log is the integrity control until a second person exists (spec 12).

Permission Action Constraint
record.review resolve a flagged or held record never on an operator you have a commercial relationship with
flag.adjudicate fact-check a disputed record not "who was right", only factual error (spec 6)
link.verify resolve a name mismatch or confirm a brand
evidence.unmask view an original image writes to the audit log every time
anomaly.review work the outlier queue the only defence against selection (spec 9)
control.assign assign records to the held sample separate person as soon as a second exists
terms.confirm confirm a terms diff is real (PHASE TWO)
commercial.manage manage the commercial page cannot also hold record.review/flag.adjudicate for that operator
config.thresholds change a launch default writes to the audit log
staff.manage grant and revoke permissions

6.1 Role bundles

Roles are bundles over the units above. payout.approve is an eleventh unit and never enters a content bundle.

Role Bundle
Founder all eleven
Moderator record.review, flag.adjudicate, link.verify, anomaly.review
Payments payout.approve
Commercial commercial.manage
Control control.assign
Admin staff.manage, config.thresholds

A user may hold several roles. One person holds all at launch. Two separations from spec 12: Commercial for an operator cannot also review or adjudicate it, and Control moves to a second person the moment one exists.

Audit log row, on every state change and every privileged action: {actor, permission_used, entity_type, entity_id, from_state, to_state, reason, timestamp}. Immutable.


7. COMPUTATION

7.1 Member-weighted median (every published median)

def slice_median(records):          # records already filtered to one slice
    by_member = group_by(records, member_id)
    member_medians = []
    eligible = []
    for member, recs in by_member:
        recs = most_recent(recs, 2)  # cap 2 per member per slice FIRST (spec 14.2)
        eligible += recs
        member_medians.append(median(elapsed_time(r) for r in recs))
    if distinct_members(eligible) < 5: return None      # gate (spec 3.13 / 14.2)
    if len(eligible) < 8:              return None       # 8-record gate on CAPPED records, not raw
    return median(member_medians)   # median across members, not across records

Payout median: only PAID and completed PART_PAID, one method, within the 180-day window. Time to resolution: every terminal state plus live records at current elapsed time, same slice.

7.2 Overdue trigger

def benchmark(casino, method):
    candidates = [casino_median(casino, method),
                  sitewide_median(method),
                  published_window(casino, method)]  # method-aware; business days -> minutes via casino tz + holidays; None if terms not held
    candidates = [c for c in candidates if c is not None]
    return min(candidates) if candidates else None

def is_overdue(record):
    b = benchmark(record.casino, record.method)
    if b is None: return False                   # stays WAITING, elapsed only
    return elapsed(record) > 1.5 * b

7.3 Rates (refusal rate, cancellation rate, deposit-again)

Deposit-again is per CASINO, not per slice (spec 5.2): the member's latest answer per casino, published as a range once 5 distinct members have answered, with no 8-record floor. The refusal and cancellation rates below are per slice.

Denominator = records in the slice that reached an ending, one per member, within the window (§2.1 denominator column). Gate: 5 distinct members. "One per member" = the member's most recent ending in the slice.

7.4 Retention

Original image destroyed 90 days after record close. Masked version kept while published. On member deletion of images, record keeps its tier marked "evidence since destroyed".

7.5 Held control sample

20 percent of each founding batch, random, assigned at logging before the outcome is known. Visible only to control.assign. Publishes on resolution, marked as a released control record. If tracked withdrawals resolve faster than untracked at the same operator/method/window, publish the gap (spec 3).


8. LAUNCH PARAMETERS

All from spec 14, repeated here as the single source for a developer. spec 14 overrides if they ever diverge.

Parameter Value
Records before a slice shows a median 8
Distinct members for that slice 5
Distinct members before a headline counter 5
Per-member cap per slice 2
Rolling window 180 days (trend is exempt)
Records per member per casino per week 5
Overdue at 1.5x benchmark
First arrival prompt day 2, then days 4, 7, 14, then weekly
Silence to Unconfirmed / Abandoned 21 days after last player reply
Draft expiry 14 days, one warning
Dispute response target 72 hours
Max evidence file 12 MB, JPEG/PNG/HEIC/PDF
OCR auto-fill confidence >= 0.85
Unmasked retention 90 days after close
Dedup key casino + member + amount within 1% + request date within 1 day
Open disputes per casino 5
Re-dispute limit 2 attempts ever
Dispute throttle correction rate under 30% after >= 5 disputes
Held control share 20%
Trend window last 10 vs previous 10, min 20 in slice, suppress under 3 members

9. QUEUES

Saved queries over the one store, not separate tables. Same transition guards fire on every path (spec 12).

Queue Source Permission Target
Anomaly review outlier clusters anomaly.review daily
Flagged content screen_flag = true record.review 24h
Name mismatches name_match = mismatch_pending link.verify 48h
Provisional casinos casino.status = provisional link.verify before index
Disputed records disputed = true flag.adjudicate 72h
Terms diffs (PHASE TWO, not launch) new snapshot differs terms.confirm weekly

10. CASINO IDENTITY

  1. First record names a casino by screenshot and confirmation. Fuzzy-match against known brands, domains and the mirror-domain list.
  2. Confident match attaches. Otherwise create a provisional casino: noindex, no stable URL, record visible only by direct link.
  3. A person confirms via the provisional queue: real operator, mirror of an existing brand, plausible licence, domain matches the licence record.
  4. Merge is lossless. Records move, the provisional URL returns 410 Gone.
  5. A rejected brand is never hard-deleted against the removal rule (spec 11). Records move to the right casino or are held unresolved.

11. OPERATOR ACCESS

An operator has no login. To set a disputed flag or use the phase-two channel, it verifies by domain-control magic link to an address on the brand's primary domain, generic providers rejected (spec 6). Guardrails in spec 14.12.


12. LEVELS

Full curve and reasoning in spec 7.1, LOCKED. The schema-relevant facts:

Source Points Cap
kyc 50 once
evidenced_withdrawal 25
arrival_proof 10
casino_link 15 once per casino
distinct_casino_deposit 20 8 casinos. OFF until the link-graph protection ships (spec 7.1). Order cannot change.
written_account 5
reminder_answer 2 10/month
corroboration_held 15 Senior only
paid_test 20

12b. TIME AND JOBS

The product is a clock. A third of the state machine only moves on timers. This is how that work runs, because two developers would otherwise each invent it differently.

One sweep, not lazy-on-read. A scheduled job runs every 5 minutes over live records (DRAFT, WAITING, PART_PAID). Derived state is never computed only at read, so a record that has gone overdue is overdue for everyone at once, not just the next viewer.

The sweep is idempotent and ordered. Each record is evaluated once per tick. Transitions are checked in a fixed order so two cannot fire in one tick: overdue before silence, silence before nothing. Overdue is a flag, so it never competes with a state. The silence clock reads ever_overdue, which latches on an earlier tick, so a record cannot be flagged overdue and abandoned in the same tick.

Timer Fires when Set by the sweep
Overdue elapsed > 1.5 * benchmark and benchmark exists set overdue = true, latch ever_overdue, freeze benchmark_value_minutes. No state change.
Reminder day 2, then 4, 7, 14, then weekly from request enqueue a push or email, record last_reminder_at
Unconfirmed 21 days since last_player_reply, ever_overdue = false, never said outstanding live to UNCONFIRMED
Abandoned 21 days since last_player_reply, ever_overdue = true or said outstanding live to ABANDONED_UNPAID
Draft expiry 14 days in DRAFT one warning at day 13, delete at 14
Image destruction 90 days after record close delete original_uri, keep masked

benchmark_value_minutes is frozen per record at the moment it goes overdue, and never recomputed. A later shift in the casino or site-wide median changes the benchmark for records not yet overdue, never for one already judged. This is why the value is stored, not just the source. A WAITING record has not frozen a value yet and is judged against the live benchmark on each tick until it crosses.

Reminder timers key off last_player_reply, not last_reminder_at. A reminder we send does not reset the silence clock, only a reply does, or the silence transitions would never fire.

Sweeps are single-writer per record. The job takes a row lock, so a player action and a sweep transition cannot race. A player confirming arrival mid-sweep wins; the sweep skips a record whose state changed since it was read.


13. STILL OPEN FOR THE BUILD

Everything in spec 14.13, plus, specific to this document:


14. API CONTRACT

Public read API, no auth. Every response carries slice, count and computed_at so a number never travels without its denominator (spec 3).

GET /casino/{id}

Returns the passport, the per-method medians, the not-paid line, the counters, the trend.

200 { casino, passport, methods: [ { method, net_position, median_minutes, count,
      distinct_members, window_days, trend } ], not_paid_line, counters, records_url }
404 casino not found
409 casino is provisional (noindex), body carries the single record only

GET /casino/{id}/records

Cursor-paginated. Filter by method, net_position, band, state, tier. Sort by requested_at or elapsed.

200 { records: [ record ], next_cursor, total }

total is a true count, not a page count. Aggregates compute over the full filtered set, not the page.

GET /ranking?method={m}

Casinos ranked on winnings payout time for one method, distinct-member weighted.

200 { method, positions: [ { rank, casino, median_minutes, count } ] }

A casino appears only if its slice clears the gate (spec 14.2).

POST /record

Create a record from a screenshot. No auth beyond an email for the clock. Lands in DRAFT.

201 { record_id, state: DRAFT, read_fields }
422 unreadable, body lists fields the player must type

POST /record/{id}/confirm-details

Player confirms what we read. DRAFT to WAITING, the clock starts.

200 { state: WAITING, benchmark_used }

POST /record/{id}/confirm

One-tap arrival confirmation. Token is single-action, single-record, expiring. Never grants account access.

200 { state } 410 token expired

Visibility rules, enforced on every read path

POST /record/{id}/link

Attach the casino account link after the first record. Proof screenshot.

200 { proof_tier } 202 name mismatch, queued for a person

POST /operator/dispute

Domain-verified operator disputes a record. Fact-check only.

201 { dispute_id, state: open } 429 dispute cap reached

POST /operator/reply

One public reply per record. Screened, throttled.

201 { reply_id } 409 reply already exists

Staff routes (authenticated, permission-gated, all audit-logged)

POST /staff/record/{id}/resolve, /flag/{id}/adjudicate, /link/{id}/verify, /casino/{id}/confirm, /casino/{id}/suspend, /terms/{id}/confirm, /control/assign. Each requires the matching permission from §6 and writes the audit row.

Errors

JSON { error, code, detail }. Codes are stable strings, not HTTP status alone.


15. DECISION LOG

The calls that are settled, why, and what they replaced. A developer who wants to change one reads here first (spec appendix has the full history).

# Decision Why Replaces
D1 Payment stops the clock, never a response any response metric is captured by whoever answers cheapest operator response time
D2 No composite score, only records and named medians a formula is an attack surface and a thing to defend 5-star Bayesian average
D3 We never contact the operator (one narrow phase-two exception) avoids the data-protection wall and the fraud-signal that slows the player's money operator notification loop
D4 Silence inherits the last known state resetting to neutral rewarded operators for making people give up silence to neutral
D5 Publish immediately, gate the statistics a submission that vanishes into a queue never brings anyone back pre-publication approval queue
D6 Account link is asked for after the first record the ten minutes of motivation should buy a record, not setup link before upload
D7 We do the redaction, not the player every task moved off the player is a record kept player crops their own shot
D8 Overdue at 1.5x the lowest of three benchmarks an ordered ladder let a slow casino grade its own homework casino-own-median benchmark
D9 Part paid enters no median until the balance is zero otherwise pay $500 of $16k fast and post a good number partial credit
D10 Disputes are fact-checks, never rulings on who was right the whole product rests on not adjudicating dispute as verdict