eleven / ocho Public

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

Plain git — no account needed to clone.

Download

Download this file

1# πŸ€– The free pool β€” a cost-governed budget for free & demo spaces
2
3Design doc, 2026-07-14. Status: **designed, not built.** Companion docs:
4`docs/demo-spaces.md` (the demo tier the pool contains) and
5`docs/pricing-bands.md` (the pricing that funds it). This doc extends the
6`docs/tier-economics.md` discipline: **formulas are the contract, numbers are
7a dated snapshot.**
8
9## The idea in one paragraph
10
11Run the free tier as an **error budget for money**. A small accounting loop
12inside the cloud provisioner's existing 6-hour meter sweep computes (a) the
13estimated daily cost of every free space and demo from a tiny published cost
14model, (b) a daily free-pool *budget* derived from a percent-of-total-cost
15ceiling, net revenue, and an operator floor, and (c) one scalar,
16`pressure = spend / budget`. Pre-agreed thresholds on that scalar drive
17graceful degradation β€” shorter demo TTLs, then a pause on **new** free
18creates β€” and **never touch an existing space**. Demos get a carve-out funded
19first from the floor, so the try-it path survives even a red pool; only
20physical box limits can ever actually block a demo. The pool is
21revenue-indexed β€” paid spaces fund it β€” and the gauge is publishable on the
22status page next to the books, making the subsidy a checkable claim.
23
24Survey honesty: no SaaS publicly runs a free tier as an explicit
25%-of-cost invariant. Every *part* is established β€” SRE error budgets with
26pre-committed exhaustion policies, cloud budget actions that actuate,
27Borg/spot-style surplus tiers, the Neon/Render/Heroku-Eco idle-suspend +
28suspend-don't-bill pattern, and the FCC Universal Service Fund as a real
29revenue-indexed pool (whose uncapped ratio ratcheting to ~38% is precisely
30the failure the X-ceiling here prevents). The composition is novel; every
31part is boring.
32
33## The budget formula
34
35```
36net_revenue_day = Ξ£ over paid ledger spaces of net_eur_year / 365 (smoothed ~90d)
37
38invariant_cap = (X/100) Γ— total_cost_day # free never exceeds X% of total cost
39revenue_pool = (Y/100) Γ— net_revenue_day # the growth engine
40base_floor = LCHAT_POOL_BASE_EUR_DAY # a pool exists at zero revenue
41
42budget_day = min( invariant_cap , max(base_floor, revenue_pool) )
43demo_budget = (D/100) Γ— budget_day # reserved for demos, funded first
44pressure = free_cost_day / budget_day
45```
46
47**Chosen parameters (Paul, 2026-07-14):** `X = 25`, `Y = 30`,
48`floor = €0.20/day`, `D = 20`. All config; changing them is philosophy, not
49engineering.
50
51Shape notes:
52
53- **X is a ceiling, revenue is the engine, the floor is the bootstrap.** At
54 zero revenue the sunk box subsidises the free tier (the spot-capacity
55 logic: the capacity is already paid for). As revenue grows, the Y-term
56 takes over. X clamps everything β€” the anti-ratchet guard: the ratio can
57 never float upward with demand; admission control absorbs excess demand
58 instead.
59- **Smoothing:** revenue is computed over trailing ~90 days of entitlement
60 state, so one big customer churning shrinks the pool gradually β€” and a
61 shrink only ever gates *admissions*; existing spaces are grandfathered by
62 construction, because no actuator touches them.
63- Demos are the cheapest thing offered (~€0.01 lifetime each), so the demo
64 carve-out is small in euros and huge in units.
65
66## The cost model
67
68Per instance and day β€” awake time, storage, noise:
69
70```
71cost_day(i) = awake_frac(i) Γ— (C_slot + C_req) # RAM/CPU share + S3 request tail while awake
72 + stored_gb(i) Γ— C_store # S3 replica (Γ— retention) or disk share
73 + C_noise # registry row, cert, wakes (β‰ˆ 0)
74
75free_cost_day = Ξ£ cost_day(i) over spaces with entitlement in ('free','demo')
76total_cost_day = C_fixed + Ξ£ cost_day(i) over ALL ledger spaces
77```
78
79Awake-hours (wall-clock) is deliberately the metered thing, not "activity":
80utilization thresholds spawn keep-alive arms races (Oracle's free-tier idle
81detector did); wall-clock awake time is what the activator already measures,
82and the one gaming vector β€” holding a WebSocket open β€” makes that space
83expensive *on paper*, which is exactly what a later per-space awake-hours cap
84would key on.
85
86Snapshot constants (2026-07, re-measure at tier-economics re-derivations):
87
88| Constant | Placeholder | Source |
89|---|---|---|
90| `C_fixed` | ~€1.17/day | whole cloud plane: box + router/home/push/cloud |
91| `C_slot` | ~€0.027/day | box Γ· 40-instance materialized capacity |
92| `C_req` | ~€0.002/day | post-#836 always-awake S3 request tail |
93| `C_store` | ~€0.0014/GB-day | S3 Γ— ~2 Litestream retention |
94| `awake_frac` free | 0.05 (assumed) | hibernate-by-default |
95| `awake_frac` demo | 0.30 (assumed) | 24h life, actively poked |
96
97Worked typicals: a hibernation-dominant free space costs **~€0.6–0.9/year**;
98a 24h demo **~€0.01 total**; a Community & Friends subscription nets
99~€39/year. The arithmetic is comfortable β€” even a 30% revenue share funds
100tens of free spaces per C&F subscription. At today's one-box scale the euro
101budget is *not* the binding constraint (RAM slots and disk are); the pool
102exists to be legible now and binding at scale.
103
104Deriving cost from a config-declared model instead of metering is a
105deliberate trade: real per-instance cost attribution is heavy machinery for
106cents. It means the gauge is an **estimate and must say so wherever
107published.**
108
109## The control loop
110
111Measured every existing meter sweep (6h) from the ledger + cost model; one
112row per sweep in a new additive `pool_meter` table
113(`ts, free_count, demo_count, paid_count, free_cost_day, total_cost_day,
114budget_day, pressure`, pruned to ~a year). Creates are gated against the
115latest reading; spikes are caught by the existing create breaker and
116controld's physical caps, and a correlated wake-storm shows up next sweep as
117a fast burn.
118
119**The pre-agreed policy** (written before the budget is ever spent β€”
120SRE-style):
121
122| Pressure | State | Behavior |
123|---|---|---|
124| < 0.8 | green | nothing. Surplus exists β€” this is the "free capacity available to anyone" signal `docs/pricing-bands.md` consumes |
125| 0.8 – 1.0 | amber | new demos get shorter TTL (24h β†’ ~8h); status gauge amber; loud log line per sweep |
126| β‰₯ 1.0 | red | **pause new free creates** β€” stable error `free_pool_full`, honest copy below. Demos continue at amber settings |
127| β‰₯ 1.5 | deep red | demos shorten further / small retry-wait. Existing spaces: still untouched |
128
129**Never actuated, ever:** suspending or deleting an existing space because of
130pool pressure. The only lifecycle ends remain the reaper's never-set-up rule
131and the demo TTL β€” pre-declared lifecycle, not budget enforcement.
132
133At red, a free create answers `503 free_pool_full`:
134
135> "The free pool is full right now. Paying supports free spaces for those
136> who can't β€” as more people upgrade, the pool grows. Check back soon, or
137> upgrade to create a space now."
138
139- Paid creates are never pool-gated (they *fund* the pool); the upgrade path
140 stays open at red.
141- **No waitlist** (decided): accounts are anonymous passkeys with no contact
142 channel, and the survey found no product running a free-tier waitlist.
143 Honest-no + the live public gauge instead β€” a refusal that's a checkable
144 claim, not a shrug.
145- Self-reclaim of the account's own dead slot still works at red (it creates
146 no net-new spend).
147
148## Messaging (decided 2026-07-14)
149
150**Aggregate form only.** The canonical line is:
151
152> **"Paying supports free spaces for those who can't."**
153
154The gauge publishes the aggregate ("paid spaces currently fund the free pool
155MΓ— over"), never a per-space "your €5 funds these two spaces" β€” the personal
156link isn't mechanically true and invites weird expectations. M is re-derived
157with the tier-economics numbers and is verifiable against the public books.
158
159## Knobs and placement
160
161```
162LCHAT_POOL_MAX_SHARE=25 # X (0/unset = pool off)
163LCHAT_POOL_REVENUE_SHARE=30 # Y
164LCHAT_POOL_BASE_EUR_DAY=0.20 # floor
165LCHAT_POOL_DEMO_SHARE=20 # D
166LCHAT_POOL_COSTS="fixed_day=1.17,slot_day=0.027,req_day=0.002,storage_gb_day=0.0014,awake_free=0.05,awake_demo=0.30"
167LCHAT_POOL_DRY_RUN=1 # rollout: compute, log, publish β€” gate nothing
168```
169
170Revenue input: an additive `net_eur_year` field per entry in the existing
171`LCHAT_STRIPE_PRICES` JSON (unknown fields are ignored by today's decode;
172entries without it contribute 0 and log a warning). With pricing bands
173(`docs/pricing-bands.md`), each bandΓ—tier entry carries its own
174`net_eur_year` β€” discounted spaces grow the pool exactly in proportion to
175what they actually pay, which is also the honest version of the marketing
176line. Once Stripe Tax ships (tax-inclusive prices), `net_eur_year` figures
177are net-of-VAT approximations; the pool tolerates that error comfortably.
178
179| Layer | Enforces |
180|---|---|
181| home | nothing pool-related (IP/PoW gates are the demo doc's) |
182| cloud | the pool: budget math, pressure, `free_pool_full` admission, demo carve-out + dynamic TTL, `pool_meter`, the reap |
183| controld | physical capacity only; the `awake` stat (phase 4). Never learns about money |
184| router/activator | hibernation; (phase 4) per-entitlement idle-window override |
185
186Existing knobs keep their jobs: `LCHAT_CLOUD_MAX_CREATES_PER_DAY` (blunt
187spike breaker), `LCHAT_CLOUD_QUOTA`, the physical caps, `LCHAT_POW_BITS`,
188the reaper family, storage caps.
189
190## Interfaces
191
192- **Demos** (`docs/demo-spaces.md`): live in the same `spaces` ledger as
193 `entitlement='demo'`; draw from the carve-out; TTL and live-cap are
194 pressure-tunable; **never S3-replicate** (decided β€” the flag is the demo
195 doc's PR 1).
196- **Pricing** (`docs/pricing-bands.md`): the pool consumes one number per
197 paid space (`net_eur_year`). "Free capacity exists" is defined as
198 `pressure < 0.8`, exposed read-only (`GET /api/pool`) so the pricing
199 surfaces and the status page share one source of truth.
200
201## Phasing
202
2031. **PR 1 β€” the gauge, no behavior change.** Parse `LCHAT_POOL_*` +
204 `net_eur_year`; compute spend/budget/pressure in the meter sweep;
205 `pool_meter` table; log line; admin-token `GET /api/pool`. Ships dark
206 (pool off unless `LCHAT_POOL_MAX_SHARE` is set). Pure budget-math unit
207 tests + a seeded meter-sweep test.
2082. **PR 2 β€” admission control.** `free_pool_full` at `pressure β‰₯ 1` in
209 `createSpaceHandler` (free creates only); home relays the message;
210 `LCHAT_POOL_DRY_RUN` gates. Tests mirror the createBudget ones.
2113. **PR 3 β€” demo integration.** Carve-out budget, pressure-tuned TTL.
212 (Sequenced with the demo doc's PRs.)
2134. **PR 4 β€” measurement.** controld stats gain `awake`; the meter samples a
214 trailing per-space `awake_frac`; measured beats assumed. Optional
215 idle-window tightening at amber.
2165. **PR 5 β€” publish.** The gauge on the status page next to the books
217 ("the free pool: N spaces, funded MΓ— over, state: green"), estimate
218 caveat included.
219
220Each PR is independently revertible; nothing before PR 2 can refuse anyone.
221The rollout follows the reaper's proven dry-run-then-arm pattern.
222