eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– Demo spaces β€” instant, no-account, 24-hour trial instances
2
3Design doc, 2026-07-14. Status: **designed, not built.** Companion docs:
4`docs/free-pool.md` (the budget that subsidises demos and free spaces) and
5`docs/pricing-bands.md` (the pricing that funds the pool). Grounding:
6`cloud.go`, `controld.go`, `reaper.go`, `home/pow.go`,
7`docs/cloud-provisioner.md`.
8
9## What a demo space is
10
11A real, fully-isolated instance β€” same binary, same E2EE, same subdomain
12scheme (`<sqid>.11ch.at`, riding the wildcard DNS + cert, so creation burns
13nothing) β€” that anyone can create **without an account**, and that the system
14**reaps unconditionally 24 hours after creation**. It is the hosted analog of
15`lchat try` trials (which mint `try-*.11relay.com` and die with the gateway):
16zero-commitment, self-destructing, honest about both.
17
18Why it exists when every account already gets one persistent free space
19(`LCHAT_CLOUD_QUOTA=1`): the demo is *try before passkey*. It removes the last
20step between "curious" and "typing in a real space" β€” and its claim path is
21the conversion funnel.
22
23A demo is deliberately **strictly worse than a free space**: shorter-lived,
24smaller, no backups. That ordering is load-bearing for abuse resistance
25(Β§ Anti-burner): nobody rational farms the worse thing when the better thing
26is one passkey away.
27
28## Lifecycle
29
301. **Create.** The home page's "Try it now" calls the provisioner with
31 `kind=demo`. No account, no email, no name. The cloud allocates an
32 `entitlement='demo'` row in the existing `spaces` ledger (same table as
33 free/paid β€” one column distinguishes them, mirroring how `'paid'` works
34 today) and controld materialises the instance. The creator lands in the
35 space with an admin session, exactly like a claimed setup link.
362. **Live.** A persistent in-app banner shows the countdown ("This demo space
37 disappears in 19 hours") with a one-tap **Claim** button. The banner is
38 inline (never a floating toast) and survives every view.
393. **Claim (the happy exit).** Claim = sign in / create a passkey account on
40 home, which converts the row `demo β†’ free` (or straight to paid): the TTL
41 is cleared, S3 replication turns on, the space starts counting against the
42 account's free quota. If the account's free slot is already taken, the
43 claim surface offers the same choices as a quota-blocked create today:
44 reclaim your own dead weight, or upgrade. Data is kept intact either way.
454. **Reap (the default exit).** At `created_at + 24h` the reaper deletes the
46 instance. Unconditionally: content does not protect a demo (unlike the
47 never-set-up rule for real spaces), because the 24h death was disclosed at
48 birth and re-disclosed on a banner the whole time.
49
50## Limits (the capability-starvation layer)
51
52Demos are sized so their value to an abuser is lower than the cost of minting
53one. Suggested defaults, all env-tunable on the cloud service:
54
55| Limit | Default | Knob |
56|---|---|---|
57| Lifetime | 24h | `LCHAT_DEMO_TTL_HOURS` (the free pool may shorten it under pressure β€” see `docs/free-pool.md Β§ amber`) |
58| Members | 5 | `LCHAT_DEMO_MAX_MEMBERS` |
59| Storage | 25 MB | demo entry in `LCHAT_CLOUD_STORAGE_CAPS` |
60| Bots / AI | none | no sidecar is ever provisioned for a demo |
61| Backups | none | see below |
62| Relay hostname, custom anything | none | demos are `<sqid>.11ch.at` only |
63
64**No backups is a hard property, not a missing feature** (decided 2026-07-14):
65
66- Reaping **skips the trash archive** β€” a deliberate, documented exception to
67 the "deleteInstance never destroys" rule, safe because the death was the
68 contract from the first second.
69- **No S3 replication, ever.** Implemented structurally (slice 1, PR #874):
70 a demo is **never activator-managed at all** β€” controld forces
71 `Hibernate=false` for demo creates even on hibernate-by-default boxes, so
72 there is no lease, no litestream replica, and no parked prefix by
73 construction. Without this, thousands of 24h demos would pay S3 PUT tails
74 and strand orphan prefixes β€” the exact request-cost class the #836 cadence
75 work eliminated. The price is that a demo holds a materialized slot for
76 its 24h life, which the live-demo cap bounds; a softer
77 "sleep locally without replicating" registry flag remains optional future
78 work if demo slot pressure ever warrants it. Claiming a demo converts it
79 to an ordinary free space, which hibernates (and replicates) normally.
80
81## Anti-burner gates (the graduated-friction ladder)
82
83The demo path must stay essentially always available to humans while being
84uneconomical to farm. Layered, in order of cheapness to evade:
85
861. **Proof-of-work.** The existing hashcash gate (`home/pow.go`,
87 `LCHAT_POW_BITS`) fronts demo creation at a higher difficulty than
88 free-space creation, and the difficulty **adapts**: bits scale up with
89 trailing-hour demo-creation velocity, so an attack makes minting expensive
90 without punishing a quiet Tuesday.
912. **One live demo per IP bucket** β€” a semaphore, not a counter. Buckets are
92 `sha256(ip, rotating_salt)` with short retention (~48h), IPv6 bucketed at
93 /56 so single-address rotation buys nothing. Only **home** ever sees the
94 IP (it already does, as the TLS terminus for signup); cloud and controld
95 see only the opaque bucket hash.
963. **Cooldown β‰₯ TTL.** After a demo dies (reaped or abandoned), its bucket
97 cannot mint another for 24h. This is the single most load-bearing rule:
98 it makes serial recreation converge to *one rolling slot* β€” no better
99 than the free tier β€” so the burner incentive is structurally gone rather
100 than merely rate-limited.
1014. **Fleet caps, separate from real capacity.** A live-demo cap
102 (`LCHAT_DEMO_MAX_LIVE`, suggested 10) and a demo creations/day budget
103 (`LCHAT_DEMO_MAX_CREATES_PER_DAY`, suggested 50), both distinct from
104 `LCHAT_CLOUD_MAX_CREATES_PER_DAY` and both tunable by the free pool's
105 pressure signal β€” so demo farming can never crowd out real signups, and
106 real-signup pressure never kills the try-it path.
1075. **Physical backstops** (controld `at_capacity`) remain the only thing that
108 can ever hard-refuse a demo; the UI answers "give us a minute" and
109 retries, which the 24h churn makes genuinely true.
110
111What we deliberately do **not** do: credit-card gates, phone/email
112verification, disposable-domain blocklists, IP reputation services,
113fingerprinting. Accounts here are anonymous passkeys by design; the ladder
114above (cost imposition + velocity limits + starved capability + separate
115pool) carries the weight that identity checks carry elsewhere.
116
117## Where each check lives
118
119| Layer | Sees | Enforces |
120|---|---|---|
121| home | IP, PoW solution | hashcash difficulty; IP-bucket semaphore + cooldown; relays `kind=demo` |
122| cloud | ledger (`entitlement='demo'`), budgets | live cap, daily budget, TTL bookkeeping, claim conversion, storage cap push |
123| controld | processes, disk | physical capacity; the no-replication registry flag |
124| reaper (cloud) | ledger + controld stats | the unconditional 24h reap, no archive |
125
126This preserves the standing privilege architecture: policy in the sandboxed
127cloud service, physics in root controld, IP-adjacent checks in home.
128
129## Phasing
130
1311. **PR 1 β€” ledger + reap.** `entitlement='demo'` rows, TTL reap rule
132 (unconditional, no archive), the no-replication registry flag. Ships dark
133 behind `LCHAT_DEMO_TTL_HOURS` unset.
1342. **PR 2 β€” the gates.** IP-bucket semaphore + cooldown in home, adaptive PoW
135 difficulty, demo caps in cloud.
1363. **PR 3 β€” the surface.** Home "Try it now", the countdown banner, the claim
137 flow (including the quota-blocked variants). *Built:* the home offer + create
138 path shipped first; the banner + claim landed next as the in-space band
139 ("Keep this space" β†’ home's `#claim=<host>` screen). The banner trusts the
140 HOME server's live status (`GET /api/spaces/demo-status`, relaying the
141 cloud's per-host read), not the boot-time entitlement env β€” a claim flips
142 the ledger without restarting the instance, so only the live read can hide
143 the band. Native apps don't show the band yet (a demo reaching a native app
144 is an add-device edge case; deliberate web-first scope).
1454. **PR 4 β€” pool coupling.** TTL and live-cap become pressure-tunable once
146 `docs/free-pool.md` PR 3 lands.
147
148Each PR is independently revertible; nothing before PR 3 is user-visible.
149