ratcloud's building blocks, each one working live, in front of you. A feature without an exhibit is not offered; a broken exhibit is seen here, not logged.
Checked · 21 green, 2 red, 0 waiting for a person to try, 2 not built yet
A red light is a promise broken right now. The zoo shows it rather than hiding it, and the card says what is wrong; the green ones still work.
Identity
1
One sign-in, every app
last seen working by johannes
Sign in once at account, and every app knows who you are without asking again.
The account shim: an app requires rc-auth.php and calls auth_user(). A signed-in browser is silently handed a token for that app; there is no sign-in page per app and no password anywhere.
2
Visitors get a real page
last worked
Someone who is not signed in sees the page, not a bounce to a sign-in screen.
See it working
You are a visitor. This whole page loaded without a sign-in.
auth_user() is null for a visitor. The app decides what a visitor sees; the platform never forces a sign-in.
3
Sign out everywhere
last seen working by johannes
Signing out at account signs you out of every app in this browser.
See it working
Sign in first; then this button signs you out of every app at once.
rc_sign_out() sends the browser to account, which ends the one session behind every app's token. No app has to be told.
4
Your name and picture follow you
last worked
Change your name and picture at account, and every app shows the new ones.
Change your name or picture at account, then reload: your entry changes too. (The robot renames itself to the time of every check, which is how this exhibit checks itself.)
Zoo Robot 17:35@zoo-robot · #4
The robot was here, on live. ·
Johannes@johannes · #113
Hello ·
rc_users($ids) turns stored ids into current names and pictures, one call per page. An app keeps ids, never copies of names or pictures.
5
Find a friend
last worked
Any app can search for people by name, including people who have never used it.
line 4: unknown key 'chanels'. Known keys: name, description, channels, cron, max_body, redirects, backup_check, smoke, timeout
Refused , commit f47587c. It still serves its last good version.
ratcloud.conf is checked before anything is applied. A bad one refuses the push, in the pusher's terminal and in RC_FACTS/manifest.json; the site keeps running.
Isolation
9
Try the doors
last worked
An app cannot open another app's files, and it cannot reach the server's own internal network.
See it working
Root tries them too, as every app, every hour:
pass21 app(s) and 21 preview uid(s), 420 pair(s), 16611 paths refused (a preview opens none of its own app's live or staging), own paths open
pass35 uid(s) (apps and previews): loopback, link-local, private ranges and the box's own addresses (but 80/443) refused; own publish port open; declared exceptions: app-builder may reach the box's public sshd (rc egress builder ssh)
Every app runs as its own unix user in a sandbox, behind a firewall. RC_DATA is the one place it can write; everything else on the box is someone else's.
Realtime
10
Live for everyone
last worked
A change one person makes appears on everyone's open page within a second, without a reload.
See it working
1107
Open this page in two tabs and press in one.
Declare a channel in ratcloud.conf; browsers listen with EventSource at /rc/sub/<name>/<id>, the app POSTs to $RC_PUBLISH/pub/<name>/<id>.
11
Private streams stay private
last worked
A live stream meant for one person can't be read by someone who guesses its name.
Add `auth` to a channel in ratcloud.conf, and the app decides who may listen: nginx asks it first, with RC_CHANNEL and RC_CHANNEL_ID.
Storage
12
Your files
last worked
You can upload a file, see it listed, and download it again unchanged.
See it working
Last roundtrip, : 4851 bytes uploaded, listed and downloaded again: matched
Open storage, upload something, and download it again.
storage keeps each person's files (B2-backed) behind their sign-in; an app links to it and never holds storage's keys. The check here uploads random bytes, lists them, downloads them and compares.
13
Apps can save files for you
saving 50 postcards failed (502): B2 refused some PUTs: {"200":46,"500":4} (last worked )
An app can save a file into your storage, under its own folder, without ever holding storage's keys.
rc_app_headers("storage", true) proves to storage which app calls and for whom; storage saves only under Apps / <app>. In bulk: app/begin gives presigned PUTs (200 a call), the bytes go straight to B2, app/commit checks them all at once, app/urls hands out downloads.
Intelligence
14
Ask a model
last worked
Any app can ask a language model a question without holding a key.
The push app keeps each person's devices (Web Push); an app asks it to notify someone with rc_app_headers("push", true).
Building
17
A new app in a minute
not built yet: waiting on the dashboard
Creating an app gives it an address, a staging twin and a repo to push to.
See it working
The dashboard's create form asks root (through its spool) for a new app: a user, both addresses with certificates, and a repo.
18
Add a laptop with one line
not built yet: waiting on the dashboard
Pasting one line into Claude Code lets that machine push; removing it stops it.
See it working
The dashboard lists each creator's machines (their ssh keys) with each one's last push; removing one revokes it.
19
Show me how
last worked
Every exhibit shows the code that makes it work.
See it working
Every card has a how link. Try the counter's or the doors': the actual file serving this page, with its sha256.
An app's code is plain files in its checkout; the zoo serves its own exhibits' files as they are on disk, so what you read is what runs.
20
Errors you can read
last worked
An app's error log is visible to its creator without a shell.
See it working
It appears below within a minute.
2026-10-06T17:25:25+00:00 ratcloud zoo[1664650]: PHP Warning: zoo exhibit 20: a deliberate warning, live, 99fd2c56 in /srv/apps/zoo/live/exhibits/20-errors-you-can-read.php on line 26
2026-10-06T17:25:25+00:00 ratcloud zoo[1665061]: PHP Warning: zoo exhibit 20: a deliberate warning, staging, d609e125 in /srv/apps/zoo/staging/exhibits/20-errors-you-can-read.php on line 26
2026-10-06T17:30:14+00:00 ratcloud zoo[1666326]: PHP Warning: zoo exhibit 20: a deliberate warning, live, d67188da in /srv/apps/zoo/live/exhibits/20-errors-you-can-read.php on line 26
2026-10-06T17:30:22+00:00 ratcloud zoo[1666326]: PHP Warning: zoo exhibit 20: a deliberate warning, staging, c9167517 in /srv/apps/zoo/staging/exhibits/20-errors-you-can-read.php on line 26
2026-10-06T17:35:24+00:00 ratcloud zoo[1667895]: PHP Warning: zoo exhibit 20: a deliberate warning, staging, 72f6be19 in /srv/apps/zoo/staging/exhibits/20-errors-you-can-read.php on line 26
2026-10-06T17:35:24+00:00 ratcloud zoo[1667901]: PHP Warning: zoo exhibit 20: a deliberate warning, live, 5f994e0c in /srv/apps/zoo/live/exhibits/20-errors-you-can-read.php on line 26
PHP warnings and job output land in RC_FACTS/log.txt within a minute, readable by the app and shown on the creator's dashboard.
21
Jobs on a clock
last worked
An app can run a script every few minutes.
See it working
Last tick: , written by the zoo's own five-minute job.
Add `cron = bin/job.php every 5m` to ratcloud.conf: root runs it as the app, never two at once, with its output in RC_FACTS/log.txt.
25
Change an app by asking
last worked
A model changes your app and shows it running on a preview address, and nothing reaches staging or live until you tap ship.
See it working
Last night the builder's robot asked, on the sample app zoo-scratch:
Make GET /ping answer with exactly the text "plover-b394a3" (plain text, status 200). Keep everything else as it is.
preview: /ping answered 200 plover-b394a3 (has the word)
staging: /ping answered 404 not found (without it)
6 turns, 14k tokens from the builder's own budget, 2026-10-06T04:10:01+00:00.
The builder app: say what should change; it edits a clone, pushes preview/run-<id> (its key can push nothing else), and the creator reviews the diff and the preview, then ships through root's ship request.
Operations
22
The zoo checks itself
red: exhibit 13 (last worked )
Every self-running exhibit is rechecked on a schedule, and the front page says when.
See it working
Checked (took 24 s).
The back of house, checked by root :
passAn app cannot read another app's data, code or repo, checked from outside as each app's user. 21 app(s) and 21 preview uid(s), 420 pair(s), 16611 paths refused (a preview opens none of its own app's live or staging), own paths open
passNo app can use admin rights (sudo), and no creator gets a shell or port forwarding, only git. 21 app user(s) and their preview users without sudo or shell; 2 creator(s): git-shell (git and rc-data only), restricted keys, one pinned sudo rule per app; 3 key(s) granted preview/* only, none of a platform app to the builder; 1 owner(s), each holding an account
passRoot never runs code an app pushed, and never writes into a directory an app owns. no root-owned file in any app's data, spool or checkout; every app process and job runs as its app
passAn account number is never reused, even after a reset or a restore. AUTOINCREMENT, never below root's high-water mark; live: next id above 246, staging: next id above 414
passOne app's sign-in token gets nothing from account when another app presents it. robot's ai token: nobody at album, itself at ai, nobody after sign-out (live, staging)
passLive stream names are prefixed by app and by environment, so staging can't reach live. 46 channel id(s) all <app>.<env>.<name>; 56 publish ports, loopback only, each reachable by its own environment's uid alone
passEvery address has a valid certificate and sends the platform's security headers. 57 addresses: valid certificate (57 days left), security headers present
passThe web server's and ssh's configs on disk pass their own tests, so a reload or a reboot brings them back. nginx -t and sshd -t pass
passNo app can take the whole box: memory, CPU and tasks are capped per app, and the disk has room. 21 app slice(s) capped (MemoryMax=512M MemoryHigh=384M CPUQuota=100% TasksMax=192); 77% of the disk free; largest data: spelarbok 5.8 MB, builder 4.3 MB, reader 1012.0 KB
passA builder run's agent is a laptop in a box: its own uid, slice and network, no app's files, and only the public internet and the box's public 22 and 443. 0 run(s) on the worker, 0 of 2 up: each its own uid in rc-agent.slice (MemoryMax=1536M MemoryHigh=1408M CPUQuota=100% TasksMax=512; 768M a run), its own network; a run's uid cannot read apps' code or data or root's state, and reaches only DNS, the public internet and the box's public 22 and 443
passEvery app directory under /srv/apps has a registry entry and every entry a directory, so no probe is left behind. 21 app(s), directories and registry agree
The zoo is an ordinary app: a five-minute job re-runs its exhibits and stores the results, and root's hourly check of the platform arrives in RC_FACTS/platform.json.
23
Last night's backup works
last worked
Everything is backed up nightly, a copy leaves the server, and the backup is opened to prove it.
See it working
backup, 172 MB, verified
read back6 guestbook signature(s), counted in the backup copy
offsite, 161.5 MB encrypted
Add `backup_check = <db> <table>` to ratcloud.conf: after each nightly backup root counts that table in the copy and writes it to RC_FACTS/backup.json.
24
The server can be replaced
last worked
The whole platform can be rebuilt on a fresh machine from the tools repo, one bundle of secrets, and one backup.
See it working
Last rehearsal : everything came back. Last one that passed: .
Nothing refers to the machine: a rehearsal restores everything onto a scratch box and checks every app answers. Its verdict reaches each app in RC_FACTS/backup.json.