Skip to main content

max / makenotwork

Raise the SyncKit cap floor to 250 GiB, where the price floor stops binding MIN_CHARGE_CENTS is $2.00 and the rate is 0.8c/GiB/month, so 250 GiB quotes at exactly $2.00 and every cap below it quotes at $2.00 as well: same money, less storage. The 10 GiB floor put 240 GiB of strictly dominated choices on the control and asked the user to pick among them. Measured prod before moving it: 17 users, zero rows in app_sync_subscriptions, zero sync_blobs. Nothing to grandfather. The floor reaches clients through the pricing endpoint's min_cap_bytes, so both audiofiles cap screens pick it up with no change. The two tests that quoted 10 and 100 GiB were exercising the dominated range; replaced with one that pins the floor to the break-even, so moving the rate or the charge floor fails a test rather than silently re-opening the range.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-21 18:33 UTC
Signed with PGP, not checked
Commit: 8d7b81bd77e3ca43578fd43b0a2738c574361cd1
Parent: f8e74ec
1 file changed, +26 insertions, -15 deletions
@@ -21,7 +21,14 @@
21 21 pub const ANNUAL_MULTIPLIER: i64 = 10;
22 22
23 23 /// Minimum and maximum storage caps the user may pick.
24 - pub const MIN_CAP_BYTES: i64 = 10 * 1024 * 1024 * 1024; // 10 GiB
24 + ///
25 + /// The floor is where `MIN_CHARGE_CENTS` stops binding: 250 GiB x 0.8c is
26 + /// exactly $2.00, so every cap below it costs the same $2.00 for less storage
27 + /// and is strictly dominated. Offering that range asks the user to choose
28 + /// against their own interest. Raised from 10 GiB on 2026-08-21, when prod
29 + /// carried no SyncKit subscriptions at all, so nothing needed grandfathering.
30 + /// Should the floor or the rate move, this constant moves with them.
31 + pub const MIN_CAP_BYTES: i64 = 250 * 1024 * 1024 * 1024; // 250 GiB
25 32 pub const MAX_CAP_BYTES: i64 = 10 * 1024 * 1024 * 1024 * 1024; // 10 TiB
26 33
27 34 /// Billing interval for a SyncKit app subscription.
@@ -94,22 +101,26 @@
94 101
95 102 #[test]
96 103 fn minimum_cap_hits_floor() {
97 - let price = quote_price_cents(gb(10), SyncBillingInterval::Monthly).unwrap();
104 + // 250 GiB x $0.008 = $2.00, exactly the floor: the cheapest cap on
105 + // offer is also the last one the floor sets the price for.
106 + let price = quote_price_cents(MIN_CAP_BYTES, SyncBillingInterval::Monthly).unwrap();
98 107 assert_eq!(price, MIN_CHARGE_CENTS);
99 108 }
100 109
101 110 #[test]
102 - fn small_cap_uses_floor_until_breakeven() {
103 - // 100 GB × $0.008 = $0.80 → below the $2 floor.
104 - let price = quote_price_cents(gb(100), SyncBillingInterval::Monthly).unwrap();
105 - assert_eq!(price, MIN_CHARGE_CENTS);
106 - }
107 -
108 - #[test]
109 - fn breakeven_around_250gb() {
110 - // 250 GB × $0.008 = $2.00 → equal to floor; storage formula starts taking over.
111 - let price = quote_price_cents(gb(250), SyncBillingInterval::Monthly).unwrap();
112 - assert_eq!(price, 200);
111 + fn no_cap_on_offer_is_dominated_by_a_larger_one() {
112 + // Why MIN_CAP_BYTES sits at 250 GiB rather than lower: below the
113 + // break-even the floor binds, so a smaller cap buys less storage for
114 + // the same money. Nothing quotable may be priced at the floor except
115 + // the minimum itself. If the rate or the floor moves, this fails and
116 + // MIN_CAP_BYTES is what needs recomputing.
117 + for cap_gib in [251, 300, 512, 1024] {
118 + let price = quote_price_cents(gb(cap_gib), SyncBillingInterval::Monthly).unwrap();
119 + assert!(
120 + price > MIN_CHARGE_CENTS,
121 + "{cap_gib} GiB still quotes at the floor, so caps below it are dominated"
122 + );
123 + }
113 124 }
114 125
115 126 #[test]
@@ -128,7 +139,7 @@
128 139
129 140 #[test]
130 141 fn below_minimum_cap_rejected() {
131 - assert!(quote_price_cents(gb(5), SyncBillingInterval::Monthly).is_err());
142 + assert!(quote_price_cents(gb(200), SyncBillingInterval::Monthly).is_err());
132 143 }
133 144
134 145 #[test]
@@ -142,7 +153,7 @@
142 153 // on either bound, or a `MAX_CAP_BYTES` that means something other than
143 154 // 10 TiB, still rejects it. These sit on the two edges instead.
144 155 assert_eq!(MAX_CAP_BYTES, gb(10 * 1024), "10 TiB, in bytes");
145 - assert_eq!(MIN_CAP_BYTES, gb(10), "10 GiB, in bytes");
156 + assert_eq!(MIN_CAP_BYTES, gb(250), "250 GiB, in bytes");
146 157 assert!(quote_price_cents(MAX_CAP_BYTES, SyncBillingInterval::Monthly).is_ok());
147 158 assert!(quote_price_cents(MAX_CAP_BYTES + 1, SyncBillingInterval::Monthly).is_err());
148 159 assert!(quote_price_cents(MIN_CAP_BYTES, SyncBillingInterval::Monthly).is_ok());