carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1# Plans, caps and packs: getting paid plans live on carloku.com
2
3Status: draft for Paul's review, 2026-10-20. Supersedes the scope (not the
4settled policies) of the billing handoff in amadan discussion #321 for the
5first paid launch.
6
7## 1. Why
8
9carloku.com charges nobody today. The previous billing effort set out to
10bill metered usage (CPU hours, egress, durability writes, storage
11byte-months) and to prove every number on an invoice. That grew to 370
12unmerged commits on `feat/carloku-backend-integration-20261003`
13(`165472b8`): about 54k lines of product code, 34 files conflicting with
14main, and a blocked unit with an unexplained test failure. None of it is
15live.
16
17Paul's direction (2026-10-20): get plans live first, worry about overages
18later. The shape that makes that possible:
19
20- **Charge for counts the console already knows, never for measured
21 usage.** An app count, a pack quantity, a plan. Nothing on an invoice
22 needs proving because nothing on an invoice was measured.
23- **Measure only to enforce.** Sizes, sends, bytes and awake hours are
24 still measured, but only to warn and to stop. A measurement that is an
25 hour late means someone hits a wall an hour late. Nobody is
26 overcharged.
27
28The moment we are building for: someone has tried Carloku, deployed a free
29app, and wants another. They hit the free limit, and they pay to lift it,
30in the console, there and then.
31
32## 2. Scope
33
34Two deliveries, in this order:
35
361. **Pricing page** on carloku.com showing Free and Personal, rendered from
37 an approved price book. Can ship before checkout works.
382. **Plans, caps, packs and checkout** in the platform: a free plan, one
39 paid annual plan (Personal) bought through Stripe Checkout, packs sold
40 as quantities on the same subscription, caps with warnings and grace
41 periods, and the abuse guards in section 8.
42
43### Goals
44
45- A new account gets the free plan and its caps, with no card.
46- A free account that hits a cap can buy Personal (and packs) in the
47 console and is lifted immediately on payment.
48- Every cap warns at 80%, allows a grace period, then refuses only the
49 capped action. Apps keep serving. Data is never deleted by a cap,
50 except what a lapsed or reduced paid account no longer pays for, and
51 only after grace, a 90-day disabled period and the Trash (9.2).
52- Existing finite Carloku accounts stay sponsored: they act as paid, at
53 no charge (settled policy, #321).
54- Plans are a general platform feature. A deployment with no plan catalog
55 configured (Tito, self-hosted boxes, dev unless switched on) behaves
56 exactly as today: no caps, no billing.
57
58### Non-goals (later, not never)
59
60- Metered usage billing and overages of any kind.
61- Team plans priced per member (Community, Business).
62- Affordability bands (see 9.3).
63- Seats as a sellable pack.
64- An always-on option on any plan.
65- Billing storage requests, replication writes or egress by volume.
66
67### Anti-goals (would make this a failure even if it works)
68
69- A surprise bill. Nothing is charged that the customer did not choose.
70- Losing a customer's data without warning, or while they are still
71 paying for it.
72- A free tier that can be farmed without limit, which makes every cap
73 meaningless.
74- Carloku-specific code paths in the platform. carloku.com is a
75 deployment that configures plans; it gets no special case.
76- Re-importing the old branch wholesale, or its review process.
77
78## 3. Decisions already made
79
80By Paul in this conversation (2026-10-20), not to be reopened by review:
81
82- Scope is (1) the pricing page and (2) flat plans plus packs. Overages
83 wait.
84- Free: **3 apps**. Free apps **sleep after 1 to 2 minutes idle**.
85- Paid apps **stay awake up to 15 minutes** after the last request.
86- **All apps hibernate.** That is what makes the economics work. No plan
87 offers always-on.
88- **Packs** raise caps: extra apps, storage, email, bandwidth. They are
89 bought, not metered.
90- **Every cap has a grace period with fair notice** before anything is cut
91 off.
92- **Bandwidth** follows the same cap pattern.
93- **After a lapse, paid-only features get a grace period, are then
94 disabled, and are eventually deleted.** Agreed details: 7 days grace,
95 90 days disabled with reminders, then the Trash's 30 days (9.2).
96- Abuse guards: account farming, things that never sleep, and counting
97 everything that runs are in v1. Phishing defences are in v1 if free and
98 paid apps share a domain. Outbound abuse starts with an audit. Hidden
99 costs start as monitoring.
100
101Settled earlier and still binding (#321 and the 2026-10-01 rulings):
102
103- Existing finite accounts are sponsored; sponsorship stays with the
104 account through ownership transfer; once limits apply, exhaustion pauses
105 safely and data is kept at Carloku's expense. No automatic charging.
106- `console.carloku.com` is the paid deployment. Tito and other
107 self-managed deployments stay billing OFF. No hostname-triggered
108 activation.
109- Three currencies, lower case: usd, eur, gbp. Default by billing country
110 (EU member states eur, GB gbp, everything else usd), with a visible
111 override. An explicit currency we do not sell is refused, never
112 defaulted. Nothing is converted.
113- A live console refuses a draft price book; approval is a new book
114 version, never an edit.
115- Stripe keys are restricted `rk_` keys in the console's EnvironmentFile,
116 never the bucket, never `carlos secrets set`.
117
118## 4. Plans and limits
119
120### 4.1 The catalog
121
122A deployment opts in by configuring a **plan catalog**: a versioned file
123(shape left to the builder; JSON beside `internal/plans` is the obvious
124home) that names each plan's caps and each pack's increment. No catalog
125configured means no caps and no billing.
126
127An account's **effective limits** are its plan's caps plus, for each pack,
128quantity times increment, never above the pack's ceiling. The console
129computes them. Every enforcement point reads effective limits, never a
130plan name.
131
132### 4.2 Numbers
133
134Paul's numbers are marked **(Paul)**. Everything else is a proposal for
135Paul to approve or change before the book is approved; the builder treats
136them as configuration, not constants.
137
138| Limit | Free | Personal | Pack adds | Hard ceiling |
139| --- | --- | --- | --- | --- |
140| Running things (apps + instances, 5.1) | 3 **(Paul)** | 25 | +5 | 200 without an operator |
141| Idle window before sleep | 90 s (1 to 2 min **(Paul)**) | 15 min **(Paul)** | none | 15 min |
142| Memory / CPU per app | 256 MB / 0.5 CPU | 512 MB / 1 CPU (today's default) | none | the existing 1 GB / 2 CPU children tier |
143| Awake hours per month (account) | 100 | 1,500 | none in v1 | n/a |
144| Database + files on box (per running thing, 5.4) | 1 GB | 10 GB | +10 GB to every running thing | 2x cap, and box safety |
145| Sidecar workers per app | 1 | 5 | none in v1 | 5 |
146| Object storage (per account) | operator-approved only, 1 GB if approved | 25 GB self-serve | +50 GB | 2x cap |
147| Email per day (account) | 50 | 1,000 | +1,000 | 10,000 without an operator |
148| Bandwidth per month (account) | 10 GB | 200 GB | +200 GB | 2x cap, then throttled |
149| Free accounts per person | 1 | n/a | n/a | n/a |
150| Custom domains | none | allowed | n/a | n/a |
151| Point-in-time database history | latest copy only | 7 days | none | n/a |
152
153Prices: Personal at the draft book's annual bases (usd 28, eur 24, gbp 22).
154Pack prices are Paul's to set; the book needs one price per pack per
155currency.
156
157## 5. The caps
158
159Every cap has the same three stages (section 6). This section says, per
160cap, what is counted, where it is enforced, and what "refused" means.
161
162### 5.1 Running things (the app cap)
163
164**Counted:** apps, temporary apps, and customer instances (including
165seeded and imported). Each counts as one. Sidecar workers do not count
166against the running-things cap; they have their own per-app cap (table
1674.2: 1 on free, 5 on Personal), enforced at `handleAPISidecarCreate`
168(`apisidecars.go:216`), and they obey the plan's idle window (5.2). Fleets
169are customer-owned boxes, not apps; free accounts cannot create fleets.
170
171**Grace and creation (codex 1.2).** Refusing to create something new is
172the cap itself, not a cut-off, so it has no grace: the warning at 80%
173is the notice. Grace protects what already runs. It applies whenever a
174lowered limit would take something away from something running.
175
176**Enforced at:**
177- `claimAppWith` (`internal/console/server.go:4052`), the single claim
178 check both create doors and temporary apps already use. The temporary
179 app cap (`apitemporary.go:24,66,167`) is the template: checked under
180 `lockAccount`, refused with 409 and `"code":"cap"`.
181- `restoreApp` (`restore.go:55`) and the claims-mode restore
182 (`claim_restore.go:120`), which do not pass through the claim.
183- Instance creation. `provision.CreateRecord`
184 (`internal/provision/provision.go:882`) is **not** a common choke point
185 (codex 1.10): claims-mode creation goes through `finishClaimCreate`
186 instead (`apiinstances.go:1658`). The check belongs in the console
187 handlers before either write: `handleAPIInstanceCreate`
188 (`apiinstances.go:987`, which the seeded and imported variants pass
189 through) and the first-route attach (`domainattach.go:237`), under the
190 same account lock as the app claim so the two counts cannot race.
191- Direct mode skips the claim (`server.go:~4030`). The builder must either
192 count those apps when their rows are filled from the bucket and apply
193 the over-cap stages, or refuse direct-mode creation for plan-governed
194 accounts. Leaving it open is not an option.
195
196**Over the cap:** creation is refused. Nothing that already runs is
197touched. An account can be over the cap only by losing a plan or a pack
198(lapse, downgrade, reduction at renewal). Then: grace (section 6), and
199after grace the **newest** running things beyond the cap are disabled
200(stop serving, show a clear page), and, if still over, deleted after the
201retention period (9.2). This is the one case where a cap leads to
202deletion.
203
204### 5.2 Idle window, and things that never sleep
205
206**Idle window.** Today `cfg.Idle` is box-wide (`CARLOS_HIBERNATE_IDLE`,
207default 15 min, `internal/activator/activator.go:101,123`), and only
208sidecar workers carry a per-route window (`rt.IdleSeconds`,
209`activator.go:3114`, used at `:4646`). The plan's idle window reaches the
210box as a per-route `IdleSeconds` set by the provision reconcile pass
211(`internal/provision/reconcile.go:441` for instances,
212`sidecar_reconcile.go:144` for sidecars) from the account's effective
213limits, and the activator honours a per-route window for every entry, not
214only workers. A route's window is the smaller of its own and the plan's.
215
216**Never sleeping is refused for plan-governed accounts:**
217- the `daemon` capability (`internal/caps/caps.go:106`), refused at
218 release promote/ship time with a message naming the plan, not just at
219 converge;
220- `SidecarManifest.Hibernate = false` (`provision/sidecar.go:56`,
221 member-settable at `apisidecars.go:614`);
222- any member-reachable path that yields an always-on route. Address-proxy
223 routes default to always-on (`provision.go:444`); the builder confirms
224 whether a member can create one and closes it if so.
225
226Operators keep `carlos route --hibernate=false` for their own needs.
227
228**Schedules.** `MinEvery` is one minute (`internal/schedule/spec.go:21`)
229and a due tick holds the host awake (`activator.go:~4655`). Free accounts
230get a minimum schedule interval of 15 minutes, validated where `ValidEvery`
231runs. Paid keeps one minute.
232
233**Awake hours.** A monthly awake-hours cap per account backstops keep-alive
234pingers, open WebSockets and mining. The source is the per-account
235`instance_hours` figure the hourly economics pass already writes to
236`usage/accounts/<acct>/<month>.json` (`economicspass.go:378-397,835`). It
237is approximate and that is acceptable: it only gates.
238
239A shorter idle window cannot stop an app that is never idle: a steady
240pinger, a long stream or an open WebSocket keeps `inflight` above zero
241(`activator.go:4650`) (codex 1.1). So after grace the account is
242**throttled** through the same per-account throttle state as bandwidth
243(5.7), its open WebSockets are closed and new upgrades refused, and each
244app gets a continuous-awake ceiling (the activator's existing run ceiling,
245`MaxRunSeconds`, applied to instances): after 15 minutes awake the app is
246put to sleep and wakes on its next request. Apps still wake and serve,
247slowly, until the month rolls or the account upgrades.
248
249### 5.3 Memory and CPU
250
251Add a third tier beside `TierDefault` and `TierChildren`
252(`internal/cgroup/cgroup.go:92`): 256 MB, CPU quota 50%. `memTier`
253(`internal/host/memtier.go`) chooses it from the route's account limits.
254The tier lists that hard-refuse unknown tiers need the new entry:
255`strict_linux.go:216`, `strict_gen_linux.go:162`, `gen.go:65`, and
256unit-backed apps' numbers in `caps/render.go`.
257
258A new app gets its plan's tier at once. An app that is already running
259when its account's limit drops (lapse, downgrade) keeps its old tier
260through the 7-day grace and moves to the lower tier at its next start
261after grace (codex 1.2). Lowering memory under a running process would
262kill it, which is the cut-off without notice the grace rule forbids. What
263is new is the **explanation**: when an app is OOM-killed under the free tier, the
264console's app page and `carlos` CLI status say so in plain words and
265point at the upgrade. A free app that cannot run in 256 MB should fail
266loudly and once, not restart-loop silently.
267
268### 5.4 Database and files on the box
269
270Today nothing stops one app filling a box's disk (single gp3 root volume,
271`infra/modules/region-host/main.tf:493`; data at `/data/<host>.db` and
272`/data/instances/<id>/`, `provision.go:2912`, `bootstrap.go:75`). This is
273a live risk whether or not anyone pays, so it is in v1.
274
275**Enforcement:** a per-app quota on the app's data directory, enforced by
276the kernel, so writes fail at the hard limit with no polling and no race.
277Filesystem quotas fit the policy exactly: a soft limit with a grace timer
278and a hard limit. The builder chooses the mechanism (ext4 project quotas,
279an XFS data volume with project quotas, or another), and the choice may
280mean new boxes get a data volume built for it.
281
282**The unit is the running thing (codex 1.5).** Each app or instance has
283its own data directory on exactly one box (`provision.go:2960`), so the
284quota is per data directory, and a storage pack raises the cap of every
285running thing on the account. This sells more total disk than an account
286pool would, but every running thing is already counted and paid for, and
287it needs no allocation across boxes.
288
289**Boxes without quota support (codex 1.6).** Existing boxes keep working
290without a quota until they are replaced. On those boxes the disk cap is
291**warn-only**: warnings and reminders, no after-grace stage, and never a
292stop or a refused wake for disk. The box-level disk alert and placement's
29310% free-disk floor (`boxchoice/policy.go:22`) remain the protection
294there. The disk cap's after-grace stage switches on per box, only where
295the quota exists. Replacing flagship boxes with quota-capable ones is a
296rollout prerequisite for that stage only, not for launch.
297
298**Counting for warnings:** a periodic size check per data directory (the
299quota's own usage report where available) feeds the 80% warning and the
300grace clock.
301
302**After grace:** writes fail (SQLite sees a full disk). Reads keep
303working. The app page says why.
304
305### 5.5 Object storage
306
307Free accounts keep today's rule: an operator approves storage
308(`internal/console/apistore_entitlement.go`, `objstore/entitlement.go`).
309Paid accounts get it self-serve up to their cap.
310
311**Counting:** `sizeProvisionedStore` (`economicspass.go:1023`) already
312lists each store bucket's bytes daily into `usage/storage/<date>.json`.
313Daily is enough.
314
315**After grace:** a `Deny s3:PutObject` statement in the store's policy
316(`storePolicyJSON`, `internal/objstore/aws.go:180`; `EnsureUser` rewrites
317the whole inline policy). Reads and deletes keep working, so the customer
318can clean up. The MinIO adapter needs the equivalent (section 10).
319
320Downloads straight from a store do not pass the edge, so they are outside
321the bandwidth cap. Storage request counts are monitored, not capped, in
322v1 (8.6).
323
324### 5.6 Email
325
326Half the mechanism exists: daily caps by tier
327(`internal/console/mailcounts.go:67`), counted from SES events, enforced by
328pausing a sender's key (`mail/walk.go:1120`) until the UTC day rolls. But
329it is **per sender, not per account** (codex 1.11): `enforceMailLimits`
330reads one sender's counts against that sender's persisted grant
331(`mailcounts.go:253`), and `mailBillingTier` (`apimail.go:245`, always
332`"free"`, #226) is only consulted when a grant is created
333(`apimail.go:1297`). v1 needs:
334
335- an account-wide daily count, summed over the account's senders, checked
336 against the account's effective email limit; at the limit every sender
337 of the account is paused;
338- the per-sender cap kept as it is (an operator's `DailyCap` still works);
339- existing grants re-derived when the account's limit changes (plan,
340 pack, lapse), not only at grant creation.
341
342Email's cap resets daily, so a week-long grace does not fit. Its grace is
343**within the day**: warn at 80%, allow up to 2x the cap, pause at 2x. The
34410,000 a day hard ceiling is not raised by packs; only an operator raises
345it, because sending reputation is shared by every tenant.
346
347### 5.7 Bandwidth
348
349**Counting:** the edge already counts response bytes per route
350(`internal/edge/usagemeter.go:36`), flushed every 5 minutes into
351`usage/edge/<box>/days/<date>.json` and summed per account per month as
352`egress_gb` by the economics pass (`economicspass.go:409-427`). Approximate
353and late by up to an hour: acceptable for a gate.
354
355**Gap:** WebSocket traffic after the upgrade is not counted
356(`edge.go:1704`). v1 accepts that and relies on the awake-hours cap.
357
358**After grace:** throttle, do not refuse. The account's routes are served
359at a reduced rate until the month rolls or the account upgrades. This
360needs a per-account throttle state that every edge can see; it travels in
361the account's limits record (section 7). Grace for a monthly cap is "the
362rest of the month, up to 2x the cap".
363
364## 6. Warnings, grace and notice
365
366One engine, used by every cap except memory:
367
3681. **Warning at 80%.** Console banner on the account and the affected
369 app, a warning line in `carlos` CLI output for commands touching the
370 account, and an email to the account's owners.
3712. **Over the cap, in grace.** Everything keeps working. A daily reminder
372 says what is over, by how much, when grace ends, and the three ways
373 out: buy a pack, upgrade, or clean up.
3743. **After grace.** Only the capped action is refused (per cap, above).
375 The app keeps serving. Data is untouched.
376
377Grace periods: 7 days for counts and sizes (apps, disk, storage);
378"the rest of the day up to 2x" for email; "the rest of the month up to 2x"
379for bandwidth and awake hours. Every grace has a hard ceiling behind it
380(table 4.2): grace never means unlimited growth.
381
382State lives in the console (one row per account per cap: first over,
383grace ends, last notified, notice received), so a reminder is sent once
384per period and a console restart does not reset a clock.
385
386**What counts as notice (codex 1.4).** Notice is received when any of
387these is recorded for an owner of the account, after the cap went over:
388the warning email was accepted for delivery; the owner loaded a console
389page that showed the banner; or the owner ran a CLI command that printed
390the warning. **An after-grace stage starts only when grace has ended and
391notice was received at least 3 days earlier.** An unattended account
392whose owners were never reached stays in grace. Two things do not wait
393for notice: hard ceilings (they protect the box and every other tenant,
394not our economics) and refusing to create something new.
395
396**Dependency:** the console cannot email account owners today; giving it
397an SES sending grant is an open security ruling (the same one holding
398console invite delivery, see memory `console-invite-delivery`). Until it
399is settled, notice is only the console banner and CLI warning.
400
401## 7. How limits reach the boxes
402
403The console is the single writer of an account's effective limits. It
404writes them to one record in the bucket per account; boxes read it the
405same way they already read `accounts/<sqid>/account.json`
406(`domains/reconcile.go:573`, exposed to the account's own boxes by
407`edge/storedoor.go:376`).
408
409The integration branch's `internal/entitlement` record
410(`entitlements/accounts/<sqid>.json`, with
411`Runtime{IdleSeconds, MaxApps, MaxMembers, MemoryTier, ...}`) is the shape
412to start from. Harvest the record; leave its writer-lineage ("authority")
413machinery behind unless the builder finds a second writer, which v1 should
414not have.
415
416Boxes use the record for: idle window, memory tier, throttle state,
417suspended routes, and the disk quota size. The console uses its own
418database for everything it enforces itself (creation caps, email,
419storage policy, custom domains, schedules).
420
421A box that cannot read the record keeps the last one it read. A box that
422has never read one for an account applies **no** caps (fail open), and
423reports it. Caps protect economics, not security; failing closed would
424take customer apps down on a bucket hiccup.
425
426## 8. Abuse guards
427
4281. **One free account per person.** `mintAccount` (`server.go:2226`) is the
429 path for every console account creation (auto-mint on first sign-in,
430 `/accounts/new`, the CLI). A person may own one free account. More
431 accounts are created as paid (checkout first) or by an operator.
432 `carlos accounts create --owner` (operator, bucket-direct) is exempt.
433 Address farming is expected; detecting farms is later, not v1.
434 The rule applies at creation only (codex 1.8). A person who owns
435 several paid accounts that later lapse keeps them all, each on free
436 limits: each was paid for, and the alternative means deleting or
437 charging. Ownership transfers are not checked against it either.
4382. **Never-sleep paths refused** (5.2).
4393. **Everything that runs counts** (5.1).
4404. **Phishing and malware.** v1 keeps one apps domain for free and paid
441 (a separate free domain would rename apps on every upgrade; see 9.2).
442 Because they share it, v1 ships: custom domains for paid accounts only
443 (gate in `attachDomainAs`, `domainattach.go:116`, above the uniqueness
444 checks per that file's ordering rule); an abuse-report address on the
445 site and in each app's footer page; and an operator takedown command
446 that suspends an app and its account's other apps with a reason.
4475. **Outbound abuse.** Units already deny private ranges and metadata
448 (`IPAddressDeny`), but the public internet is open and nothing blocks
449 outbound port 25. v1: block outbound SMTP (25) from tenant units on
450 plan-governed deployments, and write down what else is open. Further
451 egress policy is later.
4526. **Hidden costs, monitored.** Per-app wakes per day (a 90-second window
453 can turn steady light traffic into a sleep/wake cycle every few
454 minutes; wakes reuse the warm local database, `backend_exec.go:784`,
455 but still take a lease and a checkpoint), replication request rates,
456 store request counts, and ship frequency (the staging disk is never
457 reaped, amadan #122). Reported per account on the operator economics
458 view. No caps in v1.
4597. **Sponsored accounts have limits too.** "Cover current usage" means a
460 sponsored account's effective limits are, per limit, the larger of
461 Personal's cap and its measured usage, recorded once when plans switch
462 on (codex 1.9):
463 - counts and sizes (running things, disk, storage): as measured on
464 switch-on day;
465 - recurring figures (email, bandwidth, awake hours): the highest full
466 month in the three months before switch-on, from the existing
467 monthly usage statements (`usage/accounts/<acct>/<month>.json`);
468 email from the per-day counts, highest day.
469 A recorded sponsored limit may exceed a table 4.2 hard ceiling; it is
470 then an explicit operator-approved figure on that account, listed for
471 Paul before switch-on. Increases after that are reviewed separately
472 (settled policy). Nothing is invented: an account with no statement
473 history gets Personal's caps.
474
475## 9. Checkout and packs
476
477### 9.1 What gets sold
478
479One subscription per account, annual, in the account's currency:
480
481- one item: Personal, quantity 1;
482- zero or more items: one per pack type, quantity = number of packs.
483
484Adding a pack mid-year updates the item's quantity with Stripe proration.
485Reducing a pack takes effect at renewal. Upgrading from free is Stripe
486Checkout in subscription mode. Managing payment method and invoices is
487the Stripe customer portal, locked to those functions. The portal does not
488change plans or quantities; the console does.
489
490### 9.2 Lapse and downgrade
491
492A failed renewal follows Stripe's retry schedule. While Stripe retries, the
493account keeps its paid limits. When the subscription ends, the account
494drops to free limits and section 6 applies to every cap it is now over,
495with its normal grace.
496
497**What the account no longer pays for is disabled, then deleted (Paul,
4982026-10-20, codex 1.7).** This covers everything a lapse, downgrade or
499pack reduction leaves beyond free (or the remaining) limits: running
500things beyond the cap and their data, sidecars beyond the cap, attached
501custom domains, and fleets. The steps:
502
5031. **Grace, 7 days** (section 6, including its notice rule). Everything
504 keeps working.
5052. **Disabled.** Stops serving; a custom domain stops routing; a fleet
506 takes no new work. Configuration and data are kept. Paying again (or
507 buying the pack back) re-enables it at once.
5083. **Deleted, 90 days after being disabled**, with reminders 30, 7 and 1
509 days before. Deletion goes through the existing Trash
510 (`release.TrashCooloff`, 30 days, `internal/release/trash.go:33`), so
511 it can still be restored for a month. Data is gone for good about four
512 months after the lapse at the earliest.
513
514Which running things are kept is the account's choice where it makes
515one before grace ends (pick which three stay); otherwise the newest go
516first. Schedules faster than the free minimum are not deleted: after
517grace they run at the free minimum interval.
518
519**Sponsored accounts are never on this path.** They keep the settled
520policy: paused at exhaustion, data kept at Carloku's expense.
521
522### 9.3 Bands
523
524The original model scaled price by an affordability band (four bands per
525plan). v1 sells at one price per currency (band A). Bands multiply the
526catalog by four and thread through checkout metadata, intents and
527records; they can be added later as more prices without changing the
528model. (My call; Paul to confirm.)
529
530### 9.4 Code to harvest from `165472b8`
531
532Not a merge. Copy onto current main, adapt, and review as new code.
533
534- **Copy:** `internal/billingstripe` without `invoices.go` (stdlib only,
535 idempotency on every write, webhook signature verification with
536 rotation, portal, customer, catalog checks); `internal/entitlement`
537 (minus authority lineage).
538- **Adapt:** `internal/plans` down to one plan plus packs per currency, no
539 bands or countries, keeping the draft/approved gate and
540 `PublicPriceBook`; `cmd/console/billing.go` env handling with new price
541 keys (`<book>/personal/<cur>`, `<book>/pack/<name>/<cur>`); the core of
542 `billingstore` (accounts, checkout intents, book assignments, event
543 dedupe, rejected events, reviews), `billingservice`, `billinghandlers`,
544 `billingpage`, `billingoperator` (the house flag), `entitlementstore`
545 (fill limits from plan plus packs); the quantity update and fence from
546 `billingquantity`, aimed at pack items.
547- **Leave behind:** everything metered (`usage` durability,
548 `resourcebudget`, `awsbilling`, `authority`, invoices, calculation, tax
549 split, statements, overage preferences, `month.go`, `lines.go`), bands
550 and countries, the legacy-EUR path, sponsored cohort enrollment and
551 observation, and `billingretirement` unless the builder shows v1 needs
552 it.
553- **Sponsorship needs a new path (codex 1.12).** The harvested house flag
554 does not cover it on its own: at `165472b8` `queueEntitlementTx`
555 (`internal/console/entitlementstore.go:219`) publishes nothing for an
556 account with no subscription, and sponsored accounts never check out.
557 v1 needs a subscription-independent plan assignment (an operator sets
558 an account's plan, with its recorded sponsored limits from 8.7) that
559 writes the same limits record. Self-hosted deployments without a billing
560 provider use the same path (section 10).
561- **Main rework:** the old code assumes one subscription item everywhere
562 (`billingservice.go:939`, `billingquantity.go:1015`). Packs mean several
563 items. Reconciliation, intent matching and price recognition must handle
564 a set of items, each recognised against the book.
565
566Tests are rewritten for v1, not copied.
567
568### 9.5 Open decisions from the old branch that still apply
569
570From `DECISIONS-FOR-PAUL.md` on the integration branch: 1 to 5 (money on
571a refused purchase, what "settled" requires, Stripe contradicting itself,
572an unbound checkout, what the buyer sees during review), 6 to 8 recast
573for pack reductions, 9 (who is free on switch-on day: answered here,
574existing accounts are sponsored, new ones are free), and 15
575(`incomplete_expired`). The builder proposes an answer for each in the
576plan; Paul rules.
577
578### 9.6 Stripe live setup
579
580Following the old `ENABLEMENT.md` procedure, reshaped: products (Personal,
581one per pack type), prices (one per product per currency, yearly,
582licensed, tax-exclusive), the 100% house coupon, a locked portal
583configuration, the webhook (`checkout.session.completed`,
584`customer.subscription.{created,updated,deleted}`, `invoice.paid`,
585`invoice.payment_failed`), the restricted key, and the console variables.
586IE and OSS tax registrations already exist. Every live step is Paul's to
587approve; a test-mode rehearsal on dev comes first.
588
589## 10. Provider boundary
590
591- Caps, grace, notices, plan catalog and limits records are platform core:
592 no AWS or Stripe dependency. A self-hosted deployment can configure
593 plans with no billing provider at all (an operator assigns plans).
594- Stripe is a billing adapter behind the console's billing service.
595- Store write blocking is an objstore adapter operation: AWS writes a
596 Deny statement; MinIO writes its own policy equivalent.
597- Disk quotas are a host (Linux) mechanism; non-Linux builds get a stub
598 (cross-compile gate).
599- Outbound port blocking is a host mechanism, Linux only.
600
601## 11. Pricing page (delivery 1)
602
603The page lives in the carloku.com site (deployed from `bes/website`, see
604memory `platform-hostnames-209`; earlier drafts in the
605`carloku-paid-pricing` worktree, rendered from
606`copy-review/pricing/strings.json` and `public-pricebook.json`). It shows
607Free and Personal from the approved book's public export, the caps table
608in human terms, and the packs. The upgrade button goes to the console;
609until checkout is live it goes to sign-up.
610
611Gates: an approved price book (a new version, not an edit of the draft);
612copy review of every string (the 2026-09-22 approval is lost and does not
613count); the site refuses to publish from a draft book.
614
615## 12. Rollout
616
617All live steps need Paul. Dev before flagship (roll order rule).
618
6191. Ship the plan catalog, limits records and caps **dormant** (no catalog
620 configured). Nothing changes anywhere.
6212. Dev: configure a catalog; mark every existing dev account sponsored;
622 create fresh free accounts and exercise every cap through grace and
623 after grace; Stripe test mode end to end, including a lapse.
6243. Flagship: mark every existing account sponsored (house) with limits
625 recorded from current usage. Configure the catalog. New accounts are
626 free. Watch a week of warnings with after-grace stages disabled.
6274. Enable after-grace stages (only for caps whose notice reached owners,
628 section 6).
6295. Stripe live provisioning (9.6), then switch checkout on. Publish the
630 pricing page's upgrade button.
631
632## 13. Left to the builder
633
634- Catalog and limits record file shapes.
635- The disk quota mechanism and the data-volume change it needs.
636- How throttling is applied at the edge.
637- Delivery slicing, beyond the order: limits and records, then caps, then
638 notices and grace, then checkout, then the page.
639- Copy for every refusal and warning (through copy review).
640
641## 14. Review record
642
643Round 1, Astra builder check at `5ff0e014`, findings committed unedited as
644`CODEX-FINDINGS-PLANS-CAPS-1.md`. Numbering here: questions 1 to 9 are
645codex 1.1 to 1.9; the three citation errors, in order, are codex 1.10 to
6461.12. All twelve are accepted:
647
648| Finding | Where answered |
649| --- | --- |
650| 1.1 awake-hours cap cannot stop a never-idle app | 5.2: throttle, close WebSockets, continuous-awake ceiling |
651| 1.2 grace vs creation refusal and memory reductions | 5.1, 5.3 |
652| 1.3 sidecars unbounded on paid | table 4.2, 5.1 |
653| 1.4 what counts as notice | section 6 |
654| 1.5 storage unit | 5.4: per running thing |
655| 1.6 boxes without quotas | 5.4: warn-only there |
656| 1.7 paid-only features after downgrade | 9.2, Paul's ruling: grace, disable, delete after 90 days via the Trash |
657| 1.8 one free account per person after lapse | 8.1: creation only |
658| 1.9 sponsored "current usage" measurement | 8.7 |
659| 1.10 `CreateRecord` is not the instance choke point | 5.1 |
660| 1.11 email caps are per sender | 5.6 |
661| 1.12 house flag publishes no limits without a subscription | 9.4 |
662
663## Calls made by the author, for Paul to check
664
665Not discussed with Paul; each is a proposal in the document:
666
667- All numbers in table 4.2 other than those marked (Paul), and pack
668 prices left blank.
669- Free gets 256 MB / 0.5 CPU (5.3).
670- No affordability bands in v1 (9.3).
671- One apps domain for free and paid, with paid-only custom domains,
672 abuse reports and takedown instead of a separate free domain (8.4).
673- Storage cap per running thing, not an account pool (5.4).
674- Notice rule: after-grace waits for received notice plus 3 days (6).
675- Awake-hours enforcement: throttle, close WebSockets, 15-minute
676 continuous-awake ceiling (5.2).
677- Sponsored limits from measured usage, busiest of the last three months
678 for recurring figures (8.7).
679- One free account per person checked at creation only (8.1).
680- Limits fail open on a box that has never read an account's record (7).
681- Outbound port 25 blocked for tenants on plan-governed deployments (8.5).
682