pricing: codex findings on the pricing spec, unedited
GPT-6 as second reader against commit 1dc2494. Fourteen findings, seven P1, mostly on the overage path: idempotency, closed-month statements, annual-vs-monthly allowance arithmetic, ownership reattribution when a route is deleted, and two meters that do not capture what the spec prices. Committed unedited before any revision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+47
−0
docs/superpowers/specs/CODEX-FINDINGS-carloku-pricing.md+47 −0
diff --git a/docs/superpowers/specs/CODEX-FINDINGS-carloku-pricing.md b/docs/superpowers/specs/CODEX-FINDINGS-carloku-pricing.md| new file mode 100644 |
| index 000000000..0bc3f5e27 |
| --- /dev/null |
| +++ b/docs/superpowers/specs/CODEX-FINDINGS-carloku-pricing.md |
| @@ -0,0 +1,47 @@ |
| +I reviewed the spec unchanged from `1dc2494`, read both historical specs, checked the cited code, and verified the Stripe claims against official documentation. **The billing mechanism is not ready to implement as written.** No files changed. |
| + |
| +1. **[P1] Account-plus-month idempotency keys do not prevent repeat invoices across hourly runs.** |
| + Spec: “Overage,” lines 319–321. Stripe can prune keys after 24 hours; reusing a pruned key creates a new request. Identical keys also cannot distinguish multiple invoice-item writes with different parameters. The proposed account fields contain no durable billing completion record. Persist an account/month billing record with the calculation snapshot, Stripe object IDs and completion state; use operation-specific keys within that record. [Stripe idempotency documentation](https://docs.stripe.com/api/idempotent_requests). |
| + |
| +2. **[P1] The “closed month’s statement” never receives its final usage.** |
| + Spec: “Overage,” lines 301–305. [`statementsPass` selects only the current month](internal/console/economicspass.go:748). Meanwhile, [metrics rollups are written after their hour ends](internal/host/metrics_loop.go:179). September’s final hour therefore becomes available in October, when the statement pass has stopped updating September. Waiting longer before invoicing does not repair it. Add an explicit previous-month catch-up and closure stage, with a defined late-data policy, before invoicing. |
| + |
| +3. **[P1] Monthly subtraction cannot implement an annual awake-hour allowance.** |
| + Spec: price book, lines 81–83; overage calculation, lines 305–307. Personal includes 1,000 hours **per year**, but the algorithm subtracts its inclusion independently from each monthly statement. Using 600 hours in each of two months would produce no excess, despite exceeding the annual allowance by 200 hours. Dividing the allowance by twelve would instead deny the promised ability to consume annual hours unevenly. [`Statement`](internal/usage/statement.go:19) contains calendar-month totals only. Define a subscription-year allowance balance, consumption carried between invoices, anniversary boundaries, and prorated allowance grants for midyear member additions. |
| + |
| +4. **[P1] Removing a route can erase its account’s previously accrued compute usage.** |
| + Spec: “Overage” and “Exists and is live.” The pass loads the **current** registry and reattributes the entire month through [`unitOwner`](internal/console/economicspass.go:211). An unmatched unit becomes operator overhead. [Provisioning actually deletes retired routes](internal/provision/reconcile.go:1005), so removing an instance before billing moves its historical hours away from the customer on the next recomputation. Preserve ownership at collection time or retain effective-dated ownership history. Closing a statement alone cannot fix this. |
| + |
| +5. **[P1] The collection path can lose usage while the statement still reports full coverage.** |
| + Spec: lines 301–315 treat statements as billing truth. [`usageFlush` drains counters before writing](internal/host/usage_loop.go:24); failed usage writes are discarded, explicitly tolerated at modelling grade. [`CoveredDays`](internal/console/economicspass.go:245) counts a day when **any** metrics, edge or storage source exists, so a storage sweep can conceal missing egress collection. Assign an invoice-grade collection upgrade and per-meter completeness policy before charging these statements. The historical economics spec explicitly deferred that upgrade. |
| + |
| +6. **[P1] Tight durability introduces a cost that the proposed meters neither capture nor bill.** |
| + Spec: Business inclusion, line 83; request input, line 66; overage table, lines 110–114. [`requests_1k` is populated from edge HTTP requests](internal/console/economicspass.go:399), not S3 replication PUTs. Nevertheless, [`PriceAccount` multiplies it by the S3 request rate](internal/economics/compute.go:100). The approved business-model spec required metered replication writes at cost × 5; that meter is absent from the current vocabulary and this implementation list. A continuously changing database can generate substantial replication cost without comparable storage growth or HTTP traffic. Add actual replication-write attribution and an explicit inclusion/overage treatment; do not reuse the edge-request meter. |
| + |
| +7. **[P1] Direct object-store downloads bypass the priced egress meter.** |
| + Spec: egress allowances and overage rates. Apps receive [bucket coordinates and credentials](internal/objstore/walk.go:591), with [direct `s3:GetObject` permission](internal/objstore/aws.go:175). Downloads through signed S3 URLs consequently need not traverse Carlos’s edge. The economics pass measures that store’s [retained bytes](internal/console/economicspass.go:181), while egress comes exclusively from edge rows. Repeated external downloads can incur platform egress without consuming the advertised allowance or generating overage. Meter that delivery path or explicitly define and account for its exclusion. |
| + |
| +8. **[P2] Free’s required “hibernate-flush only” durability has no implementing class or assigned build step.** |
| + Spec: lines 225–229 and “Needs building.” The [existing replication classes](internal/activator/activator.go:209) are stream, five-minute and daily; the unset value means streaming. None means flush only at hibernation. The listed enforcement work names idle time, app count, storage and pool degradation, but omits replication policy. Add the flush-only class and account-plan propagation as explicit work, retaining the final flush. Merely shortening the idle window leaves Free on continuous replication. |
| + |
| +9. **[P2] The stored plan identity cannot preserve grandfathered inclusions and overage rates.** |
| + Spec: “What we store,” lines 294–297; “Review promise,” lines 379–384. A plan key and band identify a mutable plan definition, not the deal an account accepted. Updating committed plans can therefore tighten existing accounts even if their Stripe subscription price remains unchanged. The existing [scenario writer replaces its named object](internal/console/economicspass.go:1020), so a scenario name is not an immutable terms reference either. Bind subscriptions and billing calculations to a retained terms version covering allowances, rates and band factors. |
| + |
| +10. **[P2] The existing pool ledger uses hypothetical revenue, not recognised subscription revenue.** |
| + Spec: pool formula, lines 239–245; published-books data source. [`economicsview.go`](internal/console/economicsview.go:523) obtains revenue from `PriceAccount`, then [uses that number to fund the pool](internal/console/economicsview.go:567). A paid but idle annual subscriber can contribute no modelled revenue; heavy usage can generate modelled revenue regardless of payment. Add recognition of annual subscription income over its service period, with discounts and reversals accounted for, and feed that into the pool. Publishing the existing ledger unchanged would violate the spec’s own honesty rule. |
| + |
| +11. **[P2] Writing public JSON into the deployment bucket does not make it readable by the static books site.** |
| + Spec: lines 355–358. The [static handler serves an extracted release tree](internal/edge/static.go:143), not arbitrary bucket keys. A publisher writing `economics/public/*.json` therefore does not update the files served by that site or establish a browser-readable endpoint. Specify either publication through a new static release or an explicit public data endpoint. Also correct “static routes carry no instance records”: [`provision.Record`](internal/provision/provision.go:30) expressly supports `Kind: "static"` at the instance-record path. |
| + |
| +12. **[P2] “Net after Stripe” excludes fees for the Stripe products the spec selects.** |
| + Spec: lines 68 and 93–97. The numbers correctly subtract card processing alone. Standard Irish pricing additionally lists **0.7% for Billing**, **0.4% for paid standalone invoices**, and **0.5% for integrated Tax transactions where registered**. Billing alone changes the three annual net figures to approximately €23.22, €48.65 and €488.75, before Tax fees. Model the selected product fees, or document a verified custom fee arrangement. [Stripe Ireland pricing](https://stripe.com/ie/pricing). |
| + |
| +13. **[P2] The allowances and country-margin claim do not implement the stated 80/20 formula.** |
| + Spec: lines 44–47 and 135–147. Using the repository’s exact anchors × 0.92, inclusion costs are €4.49504, €9.244 and €82.504: **18.73%, 18.49% and 16.50%** of gross prices, respectively—not 20%. Scaling prices and inclusions preserves the infrastructure cost ratio, but the fixed processing fee makes Personal’s displayed margin fall from **78.73% to 76.79%** across bands. Generate allowances from the stipulated budget and distinguish the invariant resource-cost ratio from margin after fees. |
| + |
| +14. **[P2] The break-even storage figures spend revenue already needed for compute and egress.** |
| + Spec: lines 93–102. The published 92/193/1,939 GB figures equal annual net revenue divided by annual storage cost, allocating nothing to the other inclusions. With those inclusions consumed, the residual storage break-even figures are approximately **74.8/161.6/1,663.6 GB**, even before correcting the additional Stripe fees. Label the existing numbers “storage-only” or subtract the other costs before presenting them as whole-plan break-even. |
| + |
| +The rounded input rates, ×5 overage rates, base infrastructure-cost totals, and Personal’s 55–59% discounted-margin calculation reproduce under the stated assumptions. Free’s €3.45708 annual estimate supports roughly 694 accounts from the bootstrap budget **for those three modelled costs**. The meter identifiers, statement files, tier column, Go/JavaScript arithmetic mirror, pool top-up accrual and scheduled-wake convention all exist. |
| + |
| +The principal Stripe API claims are sound: Checkout’s [single discount slot](https://docs.stripe.com/payments/checkout/discounts), the stated [Adaptive Pricing settlement and currency-option constraints](https://docs.stripe.com/payments/currencies/localize-prices/adaptive-pricing?payment-ui=stripe-hosted), [zero-payment subscription handling](https://docs.stripe.com/billing/subscriptions/deferred-payment), and [no tax assessment without a relevant registration](https://docs.stripe.com/billing/taxes/migration). Quantity changes do [prorate, but positive prorations are not automatically collected immediately](https://docs.stripe.com/billing/subscriptions/prorations); the implementation should choose that collection timing explicitly. I did not verify the live Stripe account’s configuration. |
| \ No newline at end of file |