Skip to content
05 / ENGINEERING CASE STUDYFun Park Stonky · back office, web, prepaid cardsLIVE SINCE 1 OCT 2026

FunOS.

What gets checked before and after every deploy
  1. 01 Tests
  2. 02 Real Postgres
  3. 03 Review agents
  4. 04 Drift check
  5. 05 Backup
  6. 06 Deploy
  7. 07 Screenshot
  8. 08 Monitor

One person and Claude Code built the system a family entertainment park runs on: bookings, gift vouchers, prepaid cards and a two-way link to the till. Real customers’ money flows through it. This is about the discipline that makes AI-written code safe enough for that.

ClientFun Park Stonky(funparkstonky.cz)
BUILT BY DAVID RAJNOHA ·  MAY–OCT 2026
Scroll
00Project Index
Role
Sole engineerArchitecture, back office, integrations, deploys, operations
Client
Fun Park StonkyFamily entertainment park, four venues
Period
May–Oct 2026Status · live since opening day
Stack
React · Supabase · Claude CodePostgres · Edge functions · Vercel
0
Tests running in CI
0
Money tests on real Postgres
0
Database migrations
0
Commits co-written by Claude
01
The Problem
Place
Uherské Hradiště, Czech Republic
Venues
Laser Game · playground · arcade · party room
Deadline
Opening day, 1 October 2026

A family entertainment park with four venues under one roof. Bookings, gift vouchers, prepaid cards, the web and the till each lived in their own place, and none of them knew about the others. The till knew what was sold. Nobody knew whether a card topped up online would actually work at the counter. The hard part wasn’t any single screen. It was making five systems agree about money, every minute, while staff serve a queue of kids.

The park opens at a fixed date. After that, every bug is a parent at the counter with a card that says the wrong number, and a family business that has to explain it.

What had to agree
  • The till · sales, credit, points
  • The prepaid card in a customer's pocket
  • Online top-ups and voucher payments
  • Bookings from the web and from staff
  • The back office the owners run the day from
  • The customer's account on the web
“A screen that looks right is easy. Money that is right in the till, in the back office and on the customer's phone at the same time is the actual product.”
02
Thesis

AI writes the code. Discipline decides whether it ships. Every rule here exists because something once got through.

Claude Code wrote most of this system, fast. Speed was never the risk. The risk is code that compiles, passes its own tests and is still wrong about money. So the work moved: less typing, more deciding what must be true, and building machines that check it every single time.

What stays with me
  • What must be true about money, and what must never happen
  • Reading the incident, not just the diff
  • Turning every escaped bug into a test or a guard
  • Deploys, in a fixed order, with a backup first
  • Saying “not yet” when the backend isn't ready
What the machine does
  • Writes the code, the migrations and the tests
  • Runs the whole CI locally before I push
  • Reviews each change through four to six lenses
  • Breaks its own tests on purpose to prove they bite
  • Checks production against the repo, both directions
03
What shipped

Four surfaces, one database, one source of truth.

Both apps and every integration read and write one Postgres database. Access rules live in the database, not in the UI: hiding a button is not a permission.

  • Web37 routes

    Public web

    Bookings with live availability per venue, gift vouchers with online payment, birthday party requests, and a customer portal with card balance, points and online top-up. Prerendered, so it reads without JavaScript.

  • FunOS33 screens

    Back office

    Reservations, events, vouchers, card registry, customers, staff shifts and payroll, revenue pulled from the till, messages from the web, a map of customer journeys and a system health page.

  • CardTill + FunOS + web

    StonkyPass prepaid card

    Credit lives in the till's native customer credit, so paying is one scan at the counter. Points and tiers are mirrored in FunOS. A lost card is blocked at the till and in FunOS in one step, then swapped for a new one with the balance.

  • OpsRuns without me

    Operations

    A monitor that emails on failure, a canary that reads a control card at the till every hour, daily encrypted backups with a tested restore, and a check every three hours that production runs what main says.

04
Architecture

Everything meets in Postgres. Every arrow has a check.

Two React apps, one Supabase project. Business rules that touch money run inside the database, where row-level security decides who may read or write what. Edge functions talk to the outside world; scheduled jobs keep the till and FunOS in sync.

People
Customer
Web · phone · card
Staff at the counter
FunOS · till
Owners
FunOS · reports
Core
Postgres
Row-level security · append-only credit ledger
Edge functions
18 · payments, till, email
Scheduled jobs
Till sync every few minutes
Outside
Dotykačka till
Sales, credit, points — both ways
Comgate
Card payments
Resend · Sentry · PostHog
Email · errors · analytics with consent
Around it
  • CI with three jobs: build, money suite on a real database, web + browser tests
  • Daily encrypted backup, restore tested
  • Deployed-version check every 3 hours
  • Operations monitor + till canary, email on failure
05
One card, one afternoon

A top-up at the till has to show up everywhere. Without anyone typing it twice.

This is the path one prepaid card takes through the system. Four different programs write to it. If any one of them disagrees, a customer sees the wrong number.

FUN PARK STONKY
Illustrative data
CardDEMO-0042
  1. 14:02TillTop-up at the counter+500 Kč
  2. 14:04SyncMirrored into FunOS500 Kč
  3. 15:40TillLaser Game, paid by card−180 Kč
  4. 15:42SyncSpend mirrored, points added+18 b
  5. 18:10WebCustomer checks the portal320 Kč
  6. 18:10FunOSStaff see the same balance320 Kč
Balance everywhere320 Kč · 18 b

Same number in the till, the back office and the customer's phone.

What makes that true
  1. 01

    The till is the cash register of truth for credit. FunOS mirrors it and never sends the same movement back twice.

  2. 02

    Credit is a ledger of movements, never an edited balance. A correction is a new line, not a changed one.

  3. 03

    Points come only from credit spent on admission and games, by the park's own rule.

  4. 04

    Every hour a canary reads a control card at the till and compares it with FunOS.

06
Key decisions

Decisions that are boring on purpose.

The credit ledger is append-only
Balances are computed from movements. Nobody, including me, edits a balance. When something is wrong, the fix is another line, and the history stays readable.
Payment webhooks are idempotent
A payment gateway will call twice, or ten times. The same payment must credit exactly once. After repeated failures the system stops retrying on its own and asks a person.
Don't take money you can't deliver
Before a customer pays for an online top-up, the system checks it can actually find the card at the till. If it can't, the customer is never sent to the payment gate.
The UI never runs ahead of the backend
If the database doesn't accept something yet, the screen says “coming soon” instead of letting a customer finish a flow that fails at the last step. Backend goes to production first.
Permissions have two sides
Hiding a tab is not access control. Every change to roles is checked in the UI and in the database policies; a test compares the two.
Consent before cookies
Visits are measured without cookies until a visitor chooses. Reject is as easy as accept, as the Czech data protection authority expects, and revoking clears what was stored.
07
How I build with AI

“Verified” means it ran, not that someone read it.

Claude Code does the typing. The loop around it is what lets real money through: every step either runs something or produces evidence, and a change only counts as done when the evidence exists.

  1. 01Ask

    The request comes in plain Czech from the people running the park. I turn it into what must be true, and what must never happen.

  2. 02Build

    Claude Code works on a branch in its own worktree. Small phases, each one verifiable on its own.

  3. 03Run CI locally

    One script runs exactly what CI runs. If the change touches money, it refuses to finish green without the suite on a real Postgres.

  4. 04Review

    Four to six review agents read the diff through different lenses: security, correctness, edge cases, and one that sees nothing but the diff.

  5. 05Break it

    Mutation check: I break what a new test guards. If the test still passes, it was decoration and gets rewritten.

  6. 06Ship

    Drift check, database backup, migration, functions, then the web build on a hidden URL. Promoted only after it's checked.

  7. 07Look

    A screenshot of the real production screen, and the monitor after the deploy. A green build has never been the finish line.

Guards — each one born from an incident
Privileged functions vs. table policies
A function that runs with elevated rights may not let in more roles than the table itself allows.
Cross-layer consistency
Database enums vs. UI maps, UI roles vs. database roles, called functions vs. migrations.
Links that exist
Every link in an email or text message must point to a real route on the web.
Anonymous access
What a visitor without login may execute is an explicit list. Money functions aren't on it.
Skipped-test ceiling
The number of silently skipped tests may not grow.
Production drift
Production vs. repo in both directions, including which version of each function is deployed.
drift check · 19 Oct 2026
$ node scripts/prod-drift-check.mjsEdge functions (18 deployed, 18 in repo)Gateway auth of deployed functions matches configDeployed functions match main (none older)Migrations (222 total)Auth config (SMTP, email templates, rate limits)# exit 1 = drift → do not deploy
Run before every deploy and every three hours by itself. It exists because a deploy once removed a live screen that production had and the repo didn't.
Caught before customers saw it
  • A role cut in the UI only

    A change reduced roles from eight to five in the interface. The database still knew the old ones: a removed external account would have landed in the full back office. Now a test compares UI roles with database roles.

  • The ad blocker that would reload a form

    Review of the analytics change found that an ad blocker blocking the analytics file would trigger the after-deploy reload and wipe a half-filled booking. Fixed before merge, with a test.

  • A table the background job could not read

    The money suite on a fresh database showed a new table had no rights for the service account. In production, the catch-up job would never have caught anything up.

08
What broke

Three incidents, three new machines.

Each of these passed the build, the tests and a human look. None of them could be found by reading. Each one now has something that runs.

  1. 15 Aug 202601

    A deploy deleted a live screen

    What happened
    A card registration flow had been deployed by hand and never merged. The next deploy from main removed it from production. Build green, tests green, feature gone.
    What exists now
    A drift check that compares production with the repo in both directions. Exit code 1 means no deploy.
  2. 19 Aug 202602

    CI was red for two days and nobody saw

    What happened
    The failure sat behind a green test step. Migrations couldn't be applied to an empty database, and a database built from migrations alone was unusable: rights granted long ago by hand never made it into the repo.
    What exists now
    A CI job that builds the database from zero on every money change and runs the money suite against it.
  3. 9 Oct 202603

    A paid top-up that didn't arrive

    What happened
    The fix for how cards are looked up at the till was in the repo, but the payment function on production was older. A customer paid online and the credit didn't arrive. It was added by hand once both systems were checked.
    What exists now
    No payment unless the card is found first, no endless automatic retries, and a check every three hours that production runs what main says.
09
Timeline

From first commit to opening day, and after.

  1. First commit

    Rebuilding an early prototype into a production stack: own database, own hosting, tests from day one.

  2. Web hardened, payments wired

    Input validation, the payment gateway and the first CI.

  3. The till talks to FunOS

    Sales from the till land in the back office automatically.

  4. Card chain verified live

    Pairing, top-up at the till, sync, back office, web — end to end on a real card.

  5. Guards and the money suite

    Cross-layer tests and a CI job on a real database.

  6. Audit before opening

    Money paths, database rights and login limits reviewed and deployed.

  7. Opening day

    Bookings live; daily encrypted backups running.

  8. Eyes on production

    Error tracking without personal data, the operations monitor and the till canary.

  9. Lost cards and analytics

    Block and swap a card at the till and in FunOS at once; web analytics with consent.

10
Numbers

Only what was measured.

Counted on the main branch and in its CI logs on 19 October 2026. Technical numbers only: revenue, customer counts and credit volumes belong to the park, not to this page.

1,544
Back-office unit tests
+ 285 that need live credentials
626
Web unit tests
Vitest
131
Browser tests
Playwright, incl. accessibility
263
Money tests on a real Postgres
database built from zero
190
Edge function tests
Deno
6
Automated guards
each from a real incident
222
Database migrations
24,572 lines of SQL
62,711
Lines of TypeScript
without tests and generated types
194
Merged pull requests
826 commits
11
Honest limits

What this is not.

  1. 01

    Not a solo codebase. A developer on the park's side edits web content, venues and prices in the same repository — about 18 % of commits. The engineering, the money paths and operations are mine.

  2. 02

    It runs on free tiers today, for hosting and the database. Backups are mine, daily and encrypted, with a tested restore. Paid plans are on the way.

  3. 03

    Neither app runs TypeScript in strict mode yet. It's written down as debt, not hidden; switching it on is a large change of its own.

  4. 04

    One incident did reach a customer before the guard for it existed. The money was put right; the guard came after.

  5. 05

    The till's API has behaviour that isn't in its documentation. A canary checks the answers every hour, but a vendor change can still surprise us first.

  6. 06

    Web analytics started on 14 October. There is no conversion data yet, so this page claims none.

The checks are the product.

Anyone can get a model to write a booking form. What took the time is the list of things that must never happen to someone's money again — and machines that check for them on every change.