carlos / platform Public

Assimilate Tito production infra spec; add deployment topology

Tito infra design (from the titogo brainstorm session) reframed as
the first self-hosted deployment in Tito's own account. Platform spec
gains §12: three-deployment end state (Tito, Eleven self-hosted;
flagship cloud in Keymail's account serving Keymail, Woodstar,
Slopbox, Kass) and svc/mail — multi-tenant outbound email over SES.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 1d76bdb8993cae8c56090b124a461e02b63ce7df parent 57b20bf
2 files changed, +416 −1
  • docs/superpowers/specs/2026-08-01-carlos-platform-design.md +50 −1
  • docs/superpowers/specs/2026-08-01-tito-production-infra-design.md +366 −0
diff --git a/docs/superpowers/specs/2026-08-01-carlos-platform-design.md b/docs/superpowers/specs/2026-08-01-carlos-platform-design.md
index cf95c54fb..93ff8bc3f 100644
--- a/docs/superpowers/specs/2026-08-01-carlos-platform-design.md
+++ b/docs/superpowers/specs/2026-08-01-carlos-platform-design.md
@@ -290,6 +290,7 @@ consumer:
|---|---|---|
| Unfurler | fetches a URL, returns preview metadata; also the audited egress choke point (instances need no arbitrary outbound network) | stateless |
| PDF / render | takes plaintext content, returns a document | stateless, forgets it |
+| Mail (`svc/mail`) | relays outbound email to AWS SES, multi-tenant: one SES tenant + configuration set + verified sending identities per app, so reputation, quotas, and suppression are isolated per tenant | stateless in the service; honest caveat: SES itself retains delivery metadata (bounces, complaints, suppression addresses) in the platform account, scoped per tenant |
The enforceable line: **nothing that sees may store.** A seeing service is
stateless *by construction* — the platform grants it no database binding,
@@ -398,7 +399,55 @@ Assertions that fail builds, not paragraphs:
3. **Naming.** The flagship deployment, the `carlos` binary's role names,
and whether `svc/*` services get their own brand.
-## 12. Provenance
+## 12. Deployment topology and rollout (added 2026-08-01)
+
+The end state is **three deployments**, one per trust-and-billing
+boundary, each an instance of the same open platform:
+
+| Deployment | AWS account | Tenants | Role in the program |
+|---|---|---|---|
+| **Tito** | 665106592142 (Tito's, existing) | Tito Go only | Self-hosted use-case #1. Spec: [Tito production infra](2026-08-01-tito-production-infra-design.md). |
+| **Eleven** | Eleven's own account | Eleven only | Self-hosted use-case #2 — and the code quarry: the platform's edge, host agent, channels, and mergeable stores are extracted from Eleven's proven activator/updater/mesh. |
+| **Flagship cloud** | 271376211898 (adopted from Keymail — see below) | Keymail, Woodstar, Slopbox, Kass | The shared multi-tenant platform; first four customers. |
+
+**Tito and Eleven are the self-hosted story.** The Open promise ("bucket
++ N hosts + DNS", §2) is not a brochure claim to verify later — it is
+proven from day one because the platform's first two production users
+*are* self-hosters. Anything the flagship needs that a self-hoster
+cannot have is a design error, caught immediately.
+
+**The four apps are the flagship's first customers.** Keymail, Woodstar,
+Slopbox, and Kass each run dedicated infrastructure in three AWS
+accounts today (Keymail + Woodstar already share 271376211898; Slopbox
+in 051457670018; Kass in 640012953448). All of it rolls into the
+flagship: one deployment, one account, four tenants in the shared pool
+(all four are CARLOS-shaped; none needs the dedicated tier). After
+migration, the Slopbox and Kass accounts wind down.
+
+**The flagship adopts Keymail's account (271376211898)** rather than
+starting fresh. Production SES access is the scarce, slow-to-acquire
+resource, and all three candidate accounts have it (50k/day, healthy,
+verified 2026-08-01) — but Keymail's account already hosts two of the
+four tenants (only Slopbox and Kass migrate across accounts), and
+Keymail's existing SES multi-tenant work is the direct quarry for
+`svc/mail`. Tito and Eleven keep their own SES footprints in their own
+deployments (Tito's 265k/day quota stays where it is).
+
+**Outbound email is a platform service** (`svc/mail`, §7): apps send
+through the platform's multi-tenant SES rather than each owning an
+email pipeline — per-app SES tenants, configuration sets, and verified
+identities keep reputation and suppression isolated per tenant.
+
+Rollout, coarsely: (1) extract the platform from Eleven's and titogo's
+code; (2) prove it on the Tito deployment (slices in the Tito infra
+spec); (3) stand up the Eleven deployment; (4) stand up the flagship in
+271376211898 and migrate Keymail and Woodstar in place, then Slopbox
+and Kass across; (5) wind down the vacated accounts. The tofu modules
+in `infra/` are written once and instantiated three times — the
+deployments differ in variables (account, regions, pools, DNS zones),
+never in shape.
+
+## 13. Provenance
Extracted from the systems that keep rebuilding this shape — Eleven,
Keymail, Woodstar, Slopbox, Kass, Tito — and designed in conversation on
diff --git a/docs/superpowers/specs/2026-08-01-tito-production-infra-design.md b/docs/superpowers/specs/2026-08-01-tito-production-infra-design.md
new file mode 100644
index 000000000..99e3a379f
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-01-tito-production-infra-design.md
@@ -0,0 +1,366 @@
+# 🤖 Tito Production Infra — the First Self-Hosted CARLOS Deployment
+
+> 🤖 This document was written by an LLM (Claude), on the ideas, instruction,
+> and editing of humans, per [factor X](https://11factor.org/#x).
+
+**Status:** Concept spec, approved in brainstorming 2026-08-01. Revised the
+same day: the original draft placed the *flagship* deployment in Tito's AWS
+account with Tito as its first dedicated tenant. The deployment topology
+decision (platform spec §12) supersedes that framing — **Tito runs its own
+single-tenant CARLOS deployment in the Tito AWS account**, as one of the two
+use-cases that extract and prove the self-hosted story. The infrastructure
+below is unchanged by the reframe; only ownership and tenancy language moved.
+
+**Relationship to the platform spec:** the
+[CARLOS platform design](2026-08-01-carlos-platform-design.md) defines the
+substrate in the abstract. This document pins one deployment of it to the
+ground — provider, regions, buckets, DNS, money — for Tito. Where the two
+could diverge, the platform spec wins; this document only chooses among
+options it already allows.
+
+## 1. Purpose
+
+Stand up Tito's own deployment of the CARLOS platform as real, multi-region
+production infrastructure, with Tito Go as its (only) tenant. Goals,
+verbatim from the brainstorm:
+
+- All orchestration just works — no runbook between "customer signs up"
+ and "instance serving."
+- Creating an account places an instance on a host, in seconds.
+- Hosts auto-update themselves.
+- Multi-region from day one: US, EU, IE, Asia, Australia.
+- Customers choose their region, and the choice is a data-at-rest promise.
+- Shared PDF rendering (one Gotenberg, not five).
+- A mesh design, and a written-down position on multi-edge.
+
+## 2. Decisions (settled in brainstorming, 2026-08-01)
+
+1. **All-AWS fleet.** One provider, one image story, IAM roles instead of
+ keys on disk, per-region buckets. The required regions (IE, AU) rule
+ out Hetzner regardless; uniformity is the "orchestration just works"
+ property. The existing Hetzner box survives only until cutover (§12).
+2. **Control plane homes in IE (eu-west-1).** One edge serves
+ `go.tito.io` — sign-in, all admin, Classic-style `/{slug}` URLs —
+ from Ireland. Cross-region admin pays one proxied hop; buyer traffic
+ never does (§6). Replicated edges are a platform-native later, not a
+ day-one build.
+3. **Self-serve signup.** Sign-up on `go.tito.io` (existing identity
+ flow), pick slug + region, instance ready in single-digit seconds. The
+ operator CLI is the same provisioner core with a different front.
+4. **Activator runtime model, wholesale.** Instances are host-agent
+ children under the single-writer object-store lease, replicated
+ continuously, hibernated by default. Placing an instance is a data
+ write, never host surgery. (This is the platform's host agent; Tito
+ retires systemd-per-instance and the generated litestream config.)
+5. **Residency = data at rest in-region.** The instance database, WAL
+ stream, parked copy, and blobs live in the chosen region's bucket.
+ Requests may transit the IE edge; email sends via SES eu-west-1; PDFs
+ render in transit in IE. Matches the platform's residency definition
+ (spec §6): rest and execution in-region, transit unconstrained.
+6. **One box + Elastic IP per region.** The `carlos` host agent/router is
+ the front door; no load balancers. A dead box is a ~10-minute
+ restore-from-bucket runbook, rare by construction. Growth within a
+ region is another host in the pool, not new machinery.
+
+### Reconciliations with the platform spec
+
+The brainstorm reached three mechanisms via Eleven that the platform spec
+supersedes; outcomes stand, mechanisms defer to the platform:
+
+- **Auto-update** is release **channels + adopt-on-wake** (spec §4), not
+ Eleven's hourly Ed25519-manifest timer. Releases are content-addressed
+ with build attestations; advancing `stable` is one lease-guarded pointer
+ write; hosts roll instances at natural boundaries. Bake windows become
+ staged promotion (`canary → beta → stable`), which is the same safety
+ property expressed as pipeline shape rather than clock.
+- **One deployment, not five meshes.** Hosts carry a region attribute and
+ belong to pool `tito`; the routing table is derived from the bucket.
+ There is no per-region membership machinery and no global directory
+ service to build — the deployment bucket already is one.
+- **Placement is deliberately dumb** (spec §6): an instance wakes on
+ whichever eligible host answers first, weighted by load. Eleven's HRW
+ hashing is not carried over; eligibility (pool ∩ residency, enforced at
+ lease-claim) does the real work, and hibernation makes placement
+ mistakes cheap.
+
+## 3. Regions
+
+| Code | AWS region | Role |
+|---|---|---|
+| `us` | us-east-1 | instance region |
+| `eu` | eu-central-1 (Frankfurt) | instance region |
+| `ie` | eu-west-1 (Dublin) | instance region **+ edge/control plane** |
+| `ap` | ap-southeast-1 (Singapore) | instance region |
+| `au` | ap-southeast-2 (Sydney) | instance region |
+
+Region codes are customer-visible (signup picker, relocation, invoices'
+"where your data lives" line). IE being both the deployment home and an
+ordinary instance region is deliberate — Tito is Irish, and the existing
+S3/SES footprint is already eu-west-1.
+
+## 4. Buckets and IAM (residency, enforced by construction)
+
+All in the Tito AWS account (**665106592142**):
+
+- **`carlos-tito-platform-665106592142`** (eu-west-1) — the deployment
+ bucket (spec §3): apps, releases, channels, environment profiles,
+ placement pools, instance records, host heartbeats, the shared cert
+ cache (KMS-encrypted). Routing metadata only — "the edge stays global;
+ routing metadata is not resident data" (spec §6). Nothing
+ customer-shaped rests here.
+- **`carlos-tito-<region>-665106592142`** ×5 — the regional data buckets:
+ parked databases, WAL streams, single-writer leases (`lock.json` beside
+ the data it guards), blobs. **This bucket is the residency promise.**
+ In this single-tenant deployment, pool `tito` is the only pool, and the
+ regional bucket split is what carries residency; the same shape serves
+ the flagship's dedicated tiers (spec §6).
+- **`carlos-tfstate-665106592142`** — OpenTofu state (versioned, SSE,
+ native locking), per the house pattern (woodstar/slopbox/keymail).
+
+IAM makes residency structural rather than behavioral: each region's host
+role is scoped to the deployment bucket **plus its own region's data
+bucket and nothing else**. A Sydney host cannot claim a Dublin lease
+because it cannot touch the Dublin bucket — the wrong-region wake the
+spec requires to "fail loudly" fails at the IAM layer, below any code we
+could get wrong. Litestream's replication lease doubles as the takeover
+guard, as in Eleven: a host that wrongly believes it owns an instance
+loses the conditional-write race, loudly.
+
+## 5. Hosts
+
+One Graviton box (t4g.medium to start) + Elastic IP per region, from a
+stock arm64 Debian AMI + cloud-init; the `carlos` binary in host-agent
+role is the only meaningful software on it. Security group: 80/443 world,
+SSH from admin IPs; inter-host traffic is authenticated at the app layer
+(fleet token over TLS), not by network topology. Host OS patching is
+unattended-upgrades plus occasional AMI refresh via tofu. Releases ship
+per-target binaries (arm64 for the fleet, amd64 for dev and the Hetzner
+interim), which the platform's content-addressed release format already
+carries.
+
+The box is disposable by construction — all durable state is in buckets —
+so there is no backup story for hosts at all, only for buckets
+(versioning + the daily restore drill Tito already runs, generalized).
+
+## 6. Routing, DNS, and certs
+
+`tito.io` is already a Route53 zone; no delegation needed.
+
+**Tofu owns the static skeleton:** `go.tito.io` → IE edge EIP; regional
+aliases `<region>.go.tito.io` → each region's EIP; wildcard
+`*.go.tito.io` → IE as the catch-all. **The platform owns dynamic
+records:** account creation writes `<slug>.go.tito.io` CNAME → its
+region's alias (TTL 60); relocation flips it; the wildcard catches
+not-yet-propagated or unknown hosts at the edge, which proxies or 404s.
+Hard boundary: nothing account-shaped ever enters tofu state, so
+`tofu apply` is always safe and never touches a customer.
+
+Traffic shapes:
+
+- **Buyer traffic** (account subdomains, later custom domains) resolves
+ directly to the owning region's host and terminates there. In-region
+ latency; no dependency on the edge being up.
+- **Organizer/admin + root-URL traffic** (`go.tito.io/...`) terminates at
+ the IE edge, which serves control-plane surfaces itself and proxies
+ instance-bound requests to the owning region's host — one hop,
+ fleet-token-authenticated. Tito's `X-Tito-Auth-*` rule tightens
+ accordingly: instances trust those headers only from their own host's
+ socket; hosts trust them only on the authenticated inter-host channel;
+ every host strips them from public ingress.
+
+**Certs:** Let's Encrypt rate-limits per registered domain (`tito.io`),
+so per-host HTTP-01 across five regions would share one 50-certs/week
+budget. Instead the edge issues a `*.go.tito.io` wildcard via DNS-01
+against Route53 and every host consumes it through the KMS-encrypted
+cert cache in the deployment bucket (the `cert cache = S3` seam Eleven
+reserved for exactly the day two boxes share issuance). Custom domains,
+when they land, use per-host HTTP-01 on the owning region's host — their
+own registered domains, no shared budget.
+
+## 7. Provisioning and placement
+
+Signup lives on the edge (existing magic-link + TOTP identity flow),
+collects slug + region, and calls the provisioner in-process — the
+activator model means no privileged host surgery, so no separate root
+daemon. The provisioner: validates the slug against the reserved list,
+writes the instance record (app `tito`, pool `tito`, residency
+`<region>`, channel `stable`) to the deployment bucket, creates the
+Route53 record, and seeds the instance database in-region on first wake
+(schema + starter content — the `titogo seed` path). Eligible host
+agents race the lease; first claim wins. Target: account serving in
+single-digit seconds. Abuse controls day one stay boring: TOTP-enrolled
+identity required, per-IP and per-identity rate limits, reserved slugs.
+
+The operator CLI verb is the same core; support can do everything signup
+can, including create accounts in regions before self-serve exposes them.
+
+## 8. Runtime: park, wake, update
+
+The platform host agent, as specced (§3, §4): instances are children
+under the object-store lease; continuous WAL streaming to the regional
+bucket; restore only when local disk lacks the database (local disk is a
+warm cache); hibernate after a generous idle window (30m to start —
+event accounts sell for weeks and sleep for months, and hibernation is
+what makes five small boxes hold thousands of accounts); due-time wakes
+cover scheduled sends and on-sale opens, so a sleeping instance never
+misses its own schedule.
+
+Updates are channel adoption (§2 reconciliation): `stable` advances, and
+host agents roll instances at wake or in a drain-and-restart sweep for
+long-running ones. A fleet with three releases live is a normal state;
+rollback is the pointer moved backwards. Tito canaries subscribe to
+`canary/*` channels and are ordinary instances — the per-session canary
+discipline in titogo's CLAUDE.md becomes `carlos canary`.
+
+## 9. svc/pdf: Gotenberg
+
+The platform spec's PDF/render service (§7) — **seeing, therefore
+stateless by construction** — instantiated as one Gotenberg behind Caddy
+on its own t4g.small in IE: pinned image, basic auth, security group
+admitting exactly the fleet's EIPs, no bucket grant, no database binding,
+no volume. Tito instances reach it through their environment profile;
+PDF work is async attachment generation, so cross-region latency is
+invisible and one region suffices. Renders transit; nothing rests —
+residency untouched.
+
+Honest v1 note: Gotenberg is a Docker container, not a CARLOS app, so
+day one it is leased infrastructure bound as a service endpoint (the
+attached-database pattern: provisioned and bound by the platform, not
+reinvented by it). Wrapping it in a `svc/pdf` CARLOS front can come
+later if the service class mechanics want it; a second box is
+`count = 2`, not a design change.
+
+## 10. Relocation
+
+Copy the instance's prefix from source to destination regional bucket
+(excluding the lease object, or the destination would 503 until TTL),
+flip the record's residency, update DNS. The propagation window needs no
+replication — lease + proxy-to-owner cover it at the cost of one hop.
+Support-run day one; self-serve later if demand exists. This is also the
+residency escape hatch: a customer who chose wrong fixes it with one
+command, and at-rest data genuinely moves.
+
+## 11. Multi-edge: considered, deliberately deferred
+
+Three different things hide under "multi-edge":
+
+1. **Static assets at CDN edge** — cheap, compatible, worth doing when
+ event pages get heavy. CloudFront over regional origins for static
+ paths only. No architectural cost; land any time.
+2. **Replicated edges** (control-plane surfaces served near the user) —
+ platform-native eventually ("edges are replicated; sessions ride with
+ identity," spec §8), blocked on replicated identity. The IE-home
+ decision costs far-region organizers ~200–300ms on admin surfaces,
+ acceptable for admin. Revisit when organizer geography demands.
+3. **Instance-level multi-edge** (one account served from several
+ hosts) — for Eleven this is plausible because its storage is
+ append-only, `mergeable` in manifest terms. **Tito's core write is
+ the opposite:** inventory decrement under contention is exactly what
+ doesn't merge, and the platform never merges an `exclusive` store
+ (spec §5, §10). On-sale spikes are served by measured SQLite headroom
+ (200k-attendee events with room to spare) and, if ever needed, a
+ waiting-room page — not by distributing the writer. **Verdict:
+ likely never; recorded here so nobody relitigates it casually.**
+
+## 12. Cutover from the Hetzner box
+
+The current box becomes the IE region's content, not its hardware: stand
+up IE, litestream-restore the registry+identity and each instance
+database into it (the cutover *is* a restore drill — the path is proven
+daily), flip `go.tito.io` + wildcard from 62.238.45.61 to the IE EIP,
+watch, retire the box, delete its bucket prefixes. Existing accounts get
+residency `ie`. This lands only after the activator model is proven on
+canaries; until then the Hetzner box runs today's systemd model
+untouched, consuming releases as an interim update client if convenient.
+
+## 13. The tofu stack
+
+`infra/tito/` in this repo (deployment infra is platform-repo property;
+the same modules should instantiate the Eleven and flagship deployments,
+spec §12), house pattern: state in `carlos-tfstate-665106592142`,
+`use_lockfile`, AWS provider ~> 6.0, profile `titogo`. Modules:
+
+- `region` ×5 — box, EIP, SG, regional data bucket, IAM role, regional
+ DNS alias; IE instantiated with `control_plane = true`.
+- `deployment` — the deployment bucket, KMS key for the cert cache,
+ release/signing parameters.
+- `svc-pdf` — the Gotenberg box, Caddy, SG of the fleet's EIPs.
+- Static DNS records in the existing `tito.io` zone.
+
+Boundary, restated as a rule: **tofu owns boxes, buckets, IAM, and
+static DNS; the platform owns apps, releases, instances, and dynamic
+DNS.**
+
+## 14. Cost and blast radius
+
+Steady state ≈ **$180/month**: 5 × t4g.medium ≈ $135, svc/pdf t4g.small
+≈ $13, EIPs/S3/Route53/KMS ≈ $20–30. Blast radius by construction: a
+region's box dying strands only that region's instances until the
+restore runbook completes; the edge dying strands admin, signup, and
+root-URL pages but **not buyer traffic** — subdomains resolve directly
+to regional hosts, which keep serving. That asymmetry (buyers keep
+buying while admin is down) is the availability story, worth saying out
+loud in the status-page vocabulary.
+
+## 15. Rollout slices
+
+Deployment-focused slices, roughly ordered; the platform's own build
+(edge, host agent, channels — extracted from Eleven's proven activator,
+updater, and mesh code) is sliced in this repo's plans, and titogo's
+app-side conformance (manifest, park/wake, dropping systemd assumptions)
+in titogo's:
+
+1. `infra/tito/` skeleton: IE region, deployment + tfstate buckets,
+ svc/pdf, static DNS. Pure tofu; Tito's invoice work can consume
+ Gotenberg immediately, before any platform code exists.
+2. Platform core proven with titogo as the first app on a miniature
+ canary deployment (spec §9's gate).
+3. titogo-as-app: manifest, `carlos vet` conformance, activator adoption.
+4. Remaining four regions (tofu instantiation).
+5. Provisioner + self-serve signup + dynamic DNS.
+6. Hetzner → IE cutover; retire the box.
+7. Relocation command.
+
+Each slice stands alone; the fleet is never half-migrated within one.
+
+## 16. Open and resolved questions
+
+Resolved by the 2026-08-01 topology decision (platform spec §12):
+
+- ~~**Flagship co-tenancy.**~~ Eleven does not join this deployment, and
+ neither does anyone else: Tito's deployment is single-tenant and
+ self-hosted. The shared flagship is a separate deployment in a separate
+ account, serving Keymail, Slopbox, Woodstar, and Kass.
+- ~~**AWS account.**~~ This deployment lives in Tito's account
+ (665106592142), which keeps its existing S3/SES footprint and its
+ 265k/day production SES quota. The flagship gets its own account
+ (platform spec §12).
+
+Still open:
+
+1. **Library or binary?** Does titogo swap `internal/carlos` for the
+ platform as a Go library, or run beside the `carlos` binary and speak
+ its contracts? (The titogo CLAUDE.md extraction plan assumed library;
+ the platform spec's one-binary-many-roles shape suggests either
+ works.)
+2. **Identity unification.** Tito's root-domain identity (registry DB,
+ magic link + TOTP) versus the platform edge "owning identity and
+ sessions" (spec §3) — merge timing, and whether Tito's identity is
+ the seed of the platform's or a tenant of it.
+3. **Email regionality.** SES sends from eu-west-1 for all regions
+ today; fine under transit-unconstrained residency, but AU/US sending
+ identities may eventually be wanted for deliverability, not
+ residency.
+
+## 17. Provenance
+
+Concretized from the CARLOS platform design (same day) and titogo's
+brainstorming session of 2026-08-01: provider, control-plane home,
+signup model, runtime model, residency semantics, and region-entry
+topology were each chosen from explicit alternatives with trade-offs.
+Reframed the same day from "flagship with Tito as tenant" to "Tito's own
+self-hosted deployment" by the topology decision. Eleven's
+built-and-proven mesh/activator/updater/relocation code
+(`docs/planet-scale.md`, `docs/instance-router.md`,
+`docs/multi-edge.md` in elevenmessenger/messenger) is the implementation
+quarry throughout.