docs: unit-economics spec — CE API bill pull + billing-readiness section
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+66
−16
docs/superpowers/specs/2026-08-11-unit-economics-design.md+66 −16
diff --git a/docs/superpowers/specs/2026-08-11-unit-economics-design.md b/docs/superpowers/specs/2026-08-11-unit-economics-design.md| index b1415dcbf..9a03c9797 100644 |
| --- a/docs/superpowers/specs/2026-08-11-unit-economics-design.md |
| +++ b/docs/superpowers/specs/2026-08-11-unit-economics-design.md |
| @@ -29,7 +29,8 @@ Questions it must answer: |
| | Decision | Choice | |
| | --- | --- | |
| | Purpose | Modelling instrument (decision-grade) | |
| -| Cost truth | Tweakable rate card × metered usage, reconciled monthly against the real AWS bill | |
| +| Cost truth | Tweakable rate card × metered usage, reconciled against the real AWS bill pulled daily from the Cost Explorer API | |
| +| Billing later | Not built now, but pre-empted: stable meter vocabulary + materialized monthly per-account usage statements are billing's future input | |
| | Meters v1 | CPU + memory, edge requests + egress bytes, storage bytes, instance-hours / census | |
| | Knobs | Live client-side recompute + named scenarios persisted to bucket config | |
| | Scope | Generic, per-deployment, operator-gated | |
| @@ -135,18 +136,56 @@ A scenario = rate card + allocation policy + pricing mode + prices/margin |
| usage JSON). "Save scenario" persists via an operator-gated, |
| CSRF-protected console endpoint. |
| -### Bill reconcile |
| - |
| -- `economics/bills/<YYYY-MM>.json`: real monthly AWS bill, total plus |
| - by-service, `source: manual` in v1. |
| -- Entered through a small console form **and** a `carlos economics bill` |
| - operator verb (the no-box-commands rule holds either way). |
| -- Dashboard renders model-vs-actual delta per service, so rate-card drift |
| - is visible the month it happens. |
| -- Automated Cost Explorer ingestion is a filed follow-up, not v1: it needs |
| - new IAM and tagging discipline, and both AWS accounts hold non-CARLOS |
| - resources (keymail's on flagship, Tito's legacy fleet on titogo), so an |
| - unfiltered bill overstates CARLOS costs. |
| +### Bill reconcile — pulled from the Cost Explorer API |
| + |
| +- The console's daily tick calls `ce:GetCostAndUsage` (monthly |
| + granularity, grouped by service, current + previous month) and upserts |
| + `economics/bills/<YYYY-MM>.json`: total plus by-service lines, |
| + `source: costexplorer`, with a `pulledAt` stamp. Cost Explorer data |
| + lags ~24h and the API costs $0.01/call — a daily pull is both fresh |
| + enough and negligible. |
| +- **IAM prerequisite:** the box role that runs the console gains |
| + `ce:GetCostAndUsage` (tofu change in `infra/modules/region-host` / |
| + pinfra for flagship, `infra/tito` for tito). |
| +- **Attribution is a knob, not a pull-time filter.** Both AWS accounts |
| + hold non-CARLOS resources (keymail's on flagship, Tito's legacy fleet |
| + on titogo), so the pull stores the whole account's by-service lines |
| + untouched; the reconcile view lets the operator mark which service |
| + lines count as CARLOS (persisted with the scenario). An optional |
| + cost-allocation-tag filter can be configured once tagging discipline |
| + exists; it is not required for v1. |
| +- **Manual fallback:** deployments without CE access (no IAM, non-AWS) |
| + enter the bill through a small console form or the |
| + `carlos economics bill` operator verb; `source: manual` overrides a |
| + pulled month. |
| +- Dashboard renders model-vs-actual delta per service, so rate-card |
| + drift is visible the month it happens. |
| + |
| +## Billing readiness (pre-empting phase 2) |
| + |
| +Billing is **not** built now, but it is the obvious phase after these |
| +numbers are analysed, so v1 deliberately lays its inputs: |
| + |
| +- **Stable meter vocabulary.** Every meter has one canonical identifier |
| + used everywhere — usage objects, scenario prices, dashboard columns: |
| + `cpu_core_hours`, `mem_gb_hours`, `egress_gb`, `requests_1k`, |
| + `storage_gb_month`, `app_months`, `instance_hours`. A future billing |
| + engine speaks this vocabulary unchanged; renaming a meter later would |
| + orphan history, so these names are chosen once, here. |
| +- **Materialized monthly usage statements.** The console's daily tick |
| + doesn't only feed the render cache — it upserts |
| + `usage/accounts/<acct>/<YYYY-MM>.json`: the account's month-to-date |
| + totals per meter, plus coverage. These durable per-account statements |
| + are exactly what an invoicing phase consumes (statement × price sheet |
| + = invoice); they also make the accounts page cheap to render. |
| +- **Scenario → price sheet promotion.** The rate-sheet pricing mode's |
| + shape *is* a future published price sheet. Scenarios are versioned |
| + manifests, so "the pricing" at billing time is one promoted scenario, |
| + not a new schema. |
| +- **Ledger upgrade path (recorded, not built).** When invoice-grade |
| + guarantees are needed, collection points append usage events to an |
| + `internal/ledger`-backed usage ledger and statements become |
| + materializations of it. Nothing in v1's shapes blocks that upgrade. |
| ## Console surface |
| @@ -204,6 +243,11 @@ as a follow-up gap rather than built now. |
| falls back to built-in defaults with a visible warning banner. |
| - Object-store credential failures during the storage sweep mark that |
| store "unswept" for the day rather than aborting the pass. |
| +- Cost Explorer pull failures (missing IAM, API error) leave the last |
| + pulled bill in place; the reconcile view shows its `pulledAt` age and |
| + a "bill data stale / unavailable" banner, and manual entry remains |
| + available. A deployment that has never pulled shows the reconcile |
| + panel in manual-entry mode, not an error. |
| ## Testing |
| @@ -222,13 +266,19 @@ as a follow-up gap rather than built now. |
| - All additive. Boxes need the new edge binary (ship → promote → |
| converge) before egress data accrues; console deploys with the usual |
| restart rule. |
| +- The Cost Explorer IAM grant (pinfra for flagship, `infra/tito` for |
| + tito) is an operator-queue tofu apply; until it lands, the reconcile |
| + panel runs in manual-entry mode. |
| - The dashboard is useful on day one from existing CPU/mem history; |
| edge, storage and census series fill in as they accrue. |
| ## Out of scope (v1) |
| -- Payments, invoicing, or any customer-facing pricing display. |
| -- Invoice-grade usage ledger (revisit when payments arrive). |
| -- Automated Cost Explorer ingestion (filed follow-up). |
| +- Payments, invoicing, or any customer-facing pricing display — but see |
| + "Billing readiness": v1's statements and meter vocabulary are built as |
| + billing's inputs. |
| +- Invoice-grade usage ledger (upgrade path recorded above). |
| +- Cost-allocation tagging discipline (the reconcile knob covers |
| + attribution until then). |
| - Tunnel/non-edge traffic metering. |
| - Read-side `carlos economics` CLI verb (filed follow-up). |