Acceptance contract, phase 1
Version: 2 · 2026-10-02 · the criteria the phase 1 build is judged against; each names its evidence
Phase 1 is done when every row below is verified by the evidence in its last column, on the live product, not on a branch. A row that cannot be verified as written is a defect in this contract, fixed here before it is fixed in code.
Money and currencies
| # | Criterion | Evidence |
|---|---|---|
| M1 | Every money value is stored as an integer in minor units with an ISO 4217 code on the same row; no float money anywhere in the schema | packages/schema test "every money column is an integer in minor units" |
| M2 | A pot has exactly one currency; a session or tournament may be in a different currency and stores the rate to the pot and to the home currency at its date | Schema columns fx_to_home; API test logging a HKD session against a USD pot |
| M3 | Changing the home currency re-renders totals without rewriting any row | API test: change home currency, assert no session row changed, totals differ |
| M4 | A transfer between pots writes two linked rows with the FX applied stored | Schema counter_pot_id, fx_applied; API test for a USD to HKD transfer |
| M5 | Daily ECB rates for USD, HKD, GBP, PHP are present in fx_rate; a missing pair falls back to a remembered manual rate |
Cron run log; API test with an unsupported currency |
The discipline engine
| # | Criterion | Evidence |
|---|---|---|
| D1 | Default rule set per pot: floor 30, standard 40, move up at 50 of the next stake plus 100 hours, stop-loss 3, cool-off 12 h, session cap 8 h, re-entry cap 2, tournament budget 5 percent | packages/rules DEFAULT_RULES; ADR-0003 |
| D2 | Starting a session offers only the stakes the pot supports; a stake above the floor is shown greyed with the shortfall | App screen test; stakeVerdict tests |
| D3 | When the pot falls under the floor the default stake moves down and Home shows the pot size that brings it back | stakeVerdict "drop" test; screenshot of Home in that state |
| D4 | At the stop-loss the add-buy-in control is locked; continuing requires a typed reason, which is stored as a rule_break |
stopLossState tests; API test that a buy-in past the limit without a reason is refused |
| D5 | After a stop-loss or an override the next session cannot start for the cool-off period; Home shows the countdown | coolOffUntil test; API test on session_start inside the window |
| D6 | The third re-entry into one event asks for a reason; every bullet past the cap is recorded as a break | reentryGate tests; API test on tournament_entry |
| D7 | Monthly tournament spend is checked against the cap the moment a bullet is fired | tournamentBudget tests |
| D8 | Rule thresholds can be edited only when no session is live; an edit below the standard is logged as a rule change | API test editing rules with a live session (refused) and without (logged) |
| D9 | The weekly report lists breaks and overrides beside the results of those sessions | Report fixture test with two clean and two broken sessions |
| D10 | Home shows the promotion bar: roll and hours against the next stake, with the amount and hours still needed | promotionProgress tests; screenshot of Home |
| D11 | The Tournaments screen shows the max buy-in the roll supports today and which limit set it | maxTournamentBuyIn tests; screenshot |
| D12 | Sign-up asks the eight questions and the suggested setup matches suggestSetup for the same answers, including a roll held in another currency |
suggestSetup tests including the 20,000 USD at 2/5 case and a roll held in another currency; a sign-up walk-through recorded with screenshots |
| D13 | Home shows the current stake with a red down arrow when the roll is under the floor at it and a green up arrow when the next stake is open; one tap moves the stake or keeps it, and staying under a red arrow writes a rule_break |
stakeVerdict tests; a UI test tapping each button; the note in the record after staying |
Sessions, tournaments, online
| # | Criterion | Evidence |
|---|---|---|
| S1 | A live session's timer, buy-ins and rule checks work with no network; writes replay in order when the network returns | Device test in airplane mode; outbox replay test |
| S2 | An online session stores site, hands and tables instead of a venue, and reports bb/100 | Schema columns; stats test |
| S3 | A tournament is an event with entries per bullet (flight, kind, cost, outcome, payout) and days played; it stays open until closed | Schema; API test reproducing the Metro Poker Manila case: 15 bullets, one bag paid, one bag to Day 2, closed on Day 2 |
| S4 | ROI is computed per event over every bullet; open events are excluded from closed ROI and shown as in play | Stats test on the same fixture |
Media, profile, journal
| # | Criterion | Evidence |
|---|---|---|
| P1 | Receipts, photos, 60 s clips and 5 min voice memos attach to buy-ins, cash-outs, tournament days, transactions or the profile, and are uploaded straight to R2 with a signed URL | API test: signed upload URL issued, object present, media row written |
| P2 | Photos are resized and stripped of EXIF on the phone before upload; no location is stored | Device test asserting no GPS tags on the uploaded object |
| P3 | Deleting a media row deletes the R2 object | API test |
| P4 | The journal lists sessions, media, breaks and milestones in time order | Journal test on a mixed fixture |
| P5 | Everything is private by default; sharing is per item | API test: an unauthenticated read of any media URL fails after expiry; a shared item is readable |
Access
| # | Criterion | Evidence |
|---|---|---|
| A1 | Sign in with Apple, Google and passkeys works on web and in the native builds | Manual check on each, recorded with screenshots |
| A2 | Every write records an actor: app, personal token or OAuth client id | Schema audit columns; API test |
| A3 | CSV export returns every row the user owns | Export test comparing row counts |
Deployment and verification
| # | Criterion | Evidence |
|---|---|---|
| V1 | The Worker API and the web build deploy from main through the pipeline only; no manual deploys after bootstrap |
Pipeline history |
| V2 | The owner logs a real session at a Manila venue from the phone with the tunnel off, and it syncs on leaving | The owner's own session in the journal |
| V3 | The phase ends with a signed tag on app and an outcome note on the tracking issue |
Tag and note present |