cortexkit-provider-usage 0.11.0

Shared wire types for the ai-provider-quota usage.get payload: ProviderUsage, Usage, RateWindow, and account/reset metadata. Pure serde, no logic.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
//! Shared wire types for the `ai-provider-quota` module's `usage.get` payload.
//!
//! The quota module serves an array of [`ProviderUsage`] per request; ALF's
//! router (`codexbar-window-extractors.ts`), astrocyte's capacity axis, and the
//! `ck quota` renderer all consume that shape. This crate is the single
//! definition those consumers compile against, so the wire shape cannot drift
//! without a shared-crate PR every side reviews.
//!
//! # Shape, not policy
//!
//! These are pure data types. Read-time transform semantics are PRODUCER
//! behavior documented on the relevant fields but NOT enforced here:
//! - **Banked-reset relaxation:** the quota module may zero
//!   [`RateWindow::used_percent`] (the EFFECTIVE number consumers pace on) and
//!   carry the provider-reported truth in [`RateWindow::raw_used_percent`].
//!   A consumer renders whatever the wire says; a sudden `0 → high` transition
//!   is an honest disarm (credits spent / auth broke), not a glitch.
//! - **Cache-only partial arrays:** the quota module never blocks on a fetch,
//!   so a result may omit providers not yet swept. Missing ≠ zero.
//! - **Degraded entries ride in-band:** a provider fetch failure is a normal
//!   [`ProviderUsage`] carrying `error`, not a request-level failure.
//!
//! # Serialization contract consumers depend on
//! - camelCase keys (`usedPercent`, `resetsAt`, `windowMinutes`, `windowKind`,
//!   `extraRateWindows`, `rawUsedPercent`, `accountInfo`, `savedResets`,
//!   `usedCount`, `totalCount`).
//! - A healthy entry MUST NOT carry `error` (consumers skip truthy-`error`
//!   entries), so it is omitted when absent.
//! - A window is emitted when it has a `usedPercent`; `resetsAt` is OPTIONAL and
//!   omitted when the provider reports no reset (never fabricated).

use serde::{Deserialize, Serialize};

/// One rate-limit window: how much of a quota pool is spent and when it resets.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RateWindow {
    /// 0..100 percent of the window's quota consumed. This is the EFFECTIVE
    /// number consumers pace on: when banked-reset relaxation applies it is
    /// zeroed, and the provider-reported percent moves to `raw_used_percent`.
    pub used_percent: f64,
    /// The provider-reported percent when `used_percent` has been relaxed to
    /// an effective value (banked resets guarantee the window resets before
    /// the wall).
    ///
    /// **Pace on `used_percent`, not on this.** The effective number is the real
    /// headroom: a reset that is going to happen has already been accounted for.
    /// Treating this as the truer figure routes work away from an account whose
    /// credit is about to be spent — and the credit expires whether or not it is
    /// used, so the cautious-looking reading is the lossy one. Display it beside
    /// the effective number in a human-facing view, where a zero next to real
    /// consumption would otherwise look like a fault.
    ///
    /// Emitted **only where the two diverge**, so its absence means they agree
    /// and falling back to `used_percent` is exact rather than approximate.
    /// Rendering a placeholder for absence would be wrong on every unrelaxed
    /// window, which is nearly all of them.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub raw_used_percent: Option<f64>,
    /// ISO 8601 / RFC 3339 timestamp when the window resets. Omitted when the
    /// provider reports no reset (e.g. an idle session window with nothing
    /// pending) — never fabricated.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub resets_at: Option<String>,
    /// Window length in minutes. Omitted when the provider does not report one;
    /// the consumer then paces on utilization alone rather than a burn rate.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub window_minutes: Option<i64>,
    /// The period this window covers, when the upstream names it: one of the
    /// values in [`window_kind`], or a value a consumer has not seen yet.
    ///
    /// It exists for windows whose length cannot be stated in minutes. A month
    /// varies, so a monthly window carries no `window_minutes`, and without a
    /// name a consumer cannot tell it from any other window with no stated
    /// length, or from whatever a provider's other page shape puts in the same
    /// slot. The first reader matches a refusal that names its limit ("monthly
    /// usage limit reached") to the window it refers to.
    ///
    /// **Absence means the upstream did not name the period, never "some other
    /// kind".** A producer sets it only from the upstream's own label or field (a
    /// page's "Monthly usage" heading, an API key such as `seven_day`), mapped
    /// onto this vocabulary. A consumer that needs the period of an unnamed
    /// window falls back to `window_minutes`.
    ///
    /// An open string, not an enum: an unknown kind must reach the consumer
    /// intact, not fail the decode of the whole response.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub window_kind: Option<String>,
    /// Absolute consumed count in the window (e.g. tokens, requests). A count
    /// of things, so integral by contract, and only ever the upstream's own
    /// figure — never recovered from a percentage and a cap (a derived figure
    /// can carry a disagreement between two provider endpoints while wearing
    /// a type that claims exactness). Omitted when the upstream reports only
    /// a percentage. Human-facing UIs can show "10,336 / 40,000" alongside
    /// the percentage for richer context.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub used_count: Option<f64>,
    /// Absolute total cap for the window, when the upstream states one. May
    /// appear without `used_count`: the cap can be known while the consumed
    /// figure is only a percentage.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub total_count: Option<f64>,
    /// How the window's quota comes back, when the upstream STATES a mechanic.
    ///
    /// **Absence licenses nothing.** It means the upstream said nothing about
    /// replenishment — never "this is a fixed window". Most providers state
    /// nothing, so absence is the common case and carries no information.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub regeneration: Option<Regeneration>,
    /// How the window's consumption divides among the upstream's own
    /// categories, when the upstream states that split.
    ///
    /// **Absence means not fetched or not published, never "all zero".** Most
    /// providers state no split, so absence is the common case.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub breakdown: Option<UsageBreakdown>,
}

/// The values [`RateWindow::window_kind`] carries today.
///
/// Named by PERIOD, not by each upstream's own term, so a consumer branches on
/// one vocabulary across providers. A producer maps the upstream's name onto it:
/// Anthropic's `five_hour` and Ollama's "Session usage" are both
/// [`FIVE_HOUR`]; Anthropic's `seven_day` and Ollama's "Weekly usage" are both
/// [`WEEKLY`].
///
/// The list is open. A consumer must handle a value it does not know, and a
/// new period is a new constant here, never a reuse of an existing one.
pub mod window_kind {
    /// A window that rolls over hourly, as the upstream names it. Its length can
    /// still be unstated: Ollama's "Hourly usage" block gives none.
    pub const HOURLY: &str = "hourly";
    /// A five-hour window, which several upstreams call a session.
    pub const FIVE_HOUR: &str = "five_hour";
    /// A one-day window.
    pub const DAILY: &str = "daily";
    /// A seven-day window.
    pub const WEEKLY: &str = "weekly";
    /// A month-long window. Its length changes with the month, so it carries no
    /// `window_minutes`, and it resets on the upstream's billing anchor, which
    /// need not be the first of a calendar month.
    pub const MONTHLY: &str = "monthly";
}

/// A window's consumption split by category, as the upstream reports it.
///
/// First observed on Anthropic's weekly window, which splits consumption across
/// `claude_code`, `chat`, `cowork` and `other`. A consumer that sees only some
/// of an account's traffic uses the split to tell which share of a movement in
/// `used_percent` it can account for.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UsageBreakdown {
    /// When the upstream computed the split, RFC 3339. Omitted when unstated.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub as_of: Option<String>,
    /// When the window this split describes began, RFC 3339, as the upstream
    /// states it. Lets a consumer tell which window a split belongs to across a
    /// reset. Omitted when unstated.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub window_started_at: Option<String>,
    /// One row per category, in the upstream's order.
    pub rows: Vec<BreakdownRow>,
}

/// One category's part of a window's consumption.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BreakdownRow {
    /// The upstream's own category key, verbatim: never mapped or renamed.
    ///
    /// A `String` rather than an enum, because this is an observability wire: a
    /// category added upstream must arrive as itself, not fail the record.
    pub key: String,
    /// This category's share of what the window has CONSUMED, 0..=100.
    ///
    /// **A share, not a used-percent.** Across rows it sums to about 100 whatever
    /// the window's `used_percent`: a window 85% used whose consumption was all
    /// one category reads 100 on that row, not 85. The part of `used_percent` a
    /// category accounts for is `used_percent * share_percent / 100`.
    ///
    /// Omitted, never zero, when the upstream's row carries no figure.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub share_percent: Option<f64>,
}

/// A stated replenishment mechanic for a window.
///
/// Present only where an upstream describes how the quota returns. It exists
/// because the alternative — projecting a replenishment onto `resets_at` — makes
/// a continuously refilling pool indistinguishable from a hard cutoff, and the
/// projection is unrecoverable once published.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Regeneration {
    /// Which mechanic the upstream describes: `cliff`, `drip`, or `unstated`.
    ///
    /// **PACING DEPENDS ON THIS AND THE THREE ANSWERS DIFFER SHARPLY.** A rate
    /// alone cannot separate them: "1,000,000 units per 720h" describes both a
    /// lump arriving on one instant and a steady accrual every hour, and the two
    /// give opposite answers about headroom on day 14.
    ///
    /// - `cliff` — the whole amount lands at `resets_at`, and **accrual before
    ///   that instant is exactly zero**. A consumer treating this as gradual
    ///   believes it has partial headroom when it has none.
    /// - `drip` — the quota accrues continuously at `rate`, so headroom grows
    ///   between reads and an exhausted pool becomes usable again without any
    ///   reset event.
    /// - `unstated` — the upstream states that quota replenishes but nothing
    ///   establishes which mechanic. **No pacing claim exists: display the rate,
    ///   never derive headroom from it.** Same contract as `PoolFunding::Unknown`
    ///   and `PoolBasis::Unstated`, for the same reason — an honest arm keeps a
    ///   producer from guessing to satisfy the type.
    ///
    /// A plain `String` rather than an enum, and REQUIRED rather than optional.
    /// String because this is an observability wire: a variant added later must
    /// not make an old consumer drop the record that reports a state it has never
    /// seen. Required because an optional discriminator invites exactly the
    /// inference this field exists to prevent — with no value present, a consumer
    /// picks one, and the picker has less evidence than the producer.
    ///
    /// Treat an unrecognised value as `unstated`: render it, pace on nothing.
    pub mechanic: String,
    /// The stated replenishment rate, when the upstream gives one.
    ///
    /// Absent means the mechanic is described without a quantity — a real and
    /// common shape ("credits refresh monthly" with no amount). Absence here says
    /// nothing about `mechanic`, which stays authoritative.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub rate: Option<RegenerationRate>,
}

/// How much quota returns, and over what period.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RegenerationRate {
    /// Amount replenished per period, in the SAME units as `used_count` and
    /// `total_count` on the window. Not integral by contract: unlike a count, a
    /// rate is legitimately fractional (a monthly grant read per hour).
    pub amount: f64,
    /// Length of the replenishment period in minutes.
    ///
    /// Distinct from the window's own `window_minutes`, which they need not
    /// match: an observed payload states a 720h refill period on a balance whose
    /// percentage is measured against a larger total that includes a purchased
    /// pool that never refills.
    pub per_minutes: i64,
}

/// A per-model window bundled under one account (e.g. Antigravity's Geminis).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ExtraWindow {
    /// Human-facing label. Absent when the producer has no display text for this
    /// window; render `id` instead rather than dropping the entry.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// Stable identifier to match on. Absent when the producer cannot name the
    /// window stably. **Not unique across providers** — one provider's ids are
    /// model names, another's its own scope labels — so key on
    /// `(provider, id)`, never on `id` alone.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    /// The figures for this window. Absent means the provider **named a limit it
    /// could not read a figure for**, which is not the same as no limit: the
    /// entry is still evidence the limit exists.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub window: Option<RateWindow>,
}

/// The window topology for one account: up to three account-wide pools plus an
/// optional list of per-model pools.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Usage {
    /// The provider's **shortest** window, not its most constrained one. Absent
    /// when the provider reported no window of that cadence.
    ///
    /// The three slots are positions, not a ranking, and **they can have holes**:
    /// each is filled from its own optional upstream field, so `secondary` may be
    /// absent while `tertiary` is present. Walk all three plus
    /// `extra_rate_windows` rather than stopping at the first gap, and take the
    /// maximum when asking how much headroom an account has.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub primary: Option<RateWindow>,
    /// The next cadence up, typically weekly. Absent means not reported — never
    /// that the window exists at zero. See [`Usage::primary`] on slot holes.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub secondary: Option<RateWindow>,
    /// A third account-wide window where a provider has one. Absent means not
    /// reported. See [`Usage::primary`] on slot holes.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tertiary: Option<RateWindow>,
    /// Windows whose meaning has no slot — per-model pools, scoped weeklies.
    /// Absent means the provider published none.
    ///
    /// These are **real limits**, not extras in the dispensable sense: a consumer
    /// ignoring this list silently ignores whichever limits did not fit three
    /// slots.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extra_rate_windows: Option<Vec<ExtraWindow>>,
}

/// An amount of money or credit, in integer minor units.
///
/// Not a float, and the reason is not stylistic. A balance is compared against
/// zero on every routing decision that reads it, and binary floating point
/// cannot hold ordinary decimal amounts exactly — the nearest `f64` to `0.1` is
/// not `0.1`, so sums drift and a comparison near zero can fall either way. The
/// providers agree: DeepSeek and MiniMax both send decimal strings, and
/// Anthropic sends integer minor units with an exponent.
///
/// Parse a provider's own representation once, where its precision is still
/// known, rather than passing a float along and re-rendering it.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Amount {
    /// The amount in minor units: `1050` with `exponent: 2` is 10.50.
    pub minor: i64,
    /// Decimal places in `minor`. `2` for currencies with cents; `0` for whole
    /// credits or points.
    pub exponent: u8,
    /// What the amount is denominated in: a currency code like `"USD"`, or a
    /// provider's own label for its credits.
    ///
    /// A free string rather than a currency enum, because not every pool is
    /// money — some are points that convert to no currency, and an enum would
    /// force those into a currency slot or drop them.
    pub unit: String,
}

/// Where a pool's balance came from, which decides what a consumer may promise.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PoolFunding {
    /// Given by the provider: a promotion, a trial grant, a voucher. Spendable
    /// without a bill.
    Granted,
    /// Bought. Spending it costs money.
    Purchased,
    /// Included in a subscription the account already pays for.
    Subscription,
    /// The provider separates this pool but does not say what funds it, **or**
    /// the producer named a funding kind this consumer does not recognise.
    ///
    /// A correct answer rather than a failure one: some providers name their
    /// pools without defining them, and guessing the funding is how a consumer
    /// ends up spending money it meant to protect.
    ///
    /// It is also the deserialization fallback, and the two meanings genuinely
    /// agree — a funding kind added after this consumer was built is, to this
    /// consumer, of unknown funding. Without the fallback an unrecognised value
    /// fails the whole `ProviderUsage` entry rather than this one field, so a
    /// new pool kind would take an account's *usage* down with it and read as
    /// the provider being unavailable.
    #[serde(other)]
    Unknown,
}

/// How a pool's `remaining` was obtained.
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PoolBasis {
    /// The provider states this pool's remaining balance directly.
    Reported,
    /// Computed from a total and a consumption figure that covers several pools
    /// at once, so the split between them is not known.
    ///
    /// The distinction is load-bearing for any "spend only granted credits"
    /// policy: against a `Reported` pool it is exact, and against a `Derived`
    /// one it can only be a ceiling.
    Derived,
    /// No basis was stated, or one was stated that this consumer does not
    /// recognise. **Treat `remaining` as a ceiling, never as exact.**
    ///
    /// This is deliberately its own variant rather than folding an unrecognised
    /// value into [`Self::Derived`]. Both are read conservatively, so the
    /// spending behaviour is the same either way — but `Derived` is a statement
    /// about how a number was obtained, and answering "I do not know" with it
    /// would have the producer assert a fact it does not hold. That is the
    /// failure this type exists to prevent, one level up.
    ///
    /// Reading it conservatively is safe in the direction that matters: an
    /// exact remainder treated as a ceiling under-spends, while a ceiling
    /// treated as exact spends money that may not be there.
    #[serde(other)]
    Unstated,
}

/// A prepaid balance or credit pool on an account.
///
/// Plural by necessity: one figure cannot express "9.50 granted and 40
/// purchased", which is exactly the distinction a consumer needs to spend the
/// first without spending the second.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Pool {
    /// The provider's own name for this pool, never one invented here.
    ///
    /// Providers separate pools without always defining them — a wallet may list
    /// voucher, cash and credit balances and document none of them. Passing the
    /// provider's name through lets a consumer decide; renaming one `granted`
    /// would be inventing the label a spend policy keys on.
    pub id: String,
    /// Human-readable name for display.
    pub label: String,
    /// What funds this pool.
    pub funding: PoolFunding,
    /// What is left, when it can be established.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub remaining: Option<Amount>,
    /// The pool's size, when the provider reports one.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub total: Option<Amount>,
    /// How `remaining` was obtained. Read it before acting on `remaining`.
    pub basis: PoolBasis,
    /// Whether the provider says this pool may currently be drawn on.
    ///
    /// Read from the provider, never inferred from `remaining > 0`: a pool can
    /// be non-empty and closed, which several providers publish directly through
    /// their own enable flags. Absent means the provider does not say.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub spendable: Option<bool>,
    /// When the pool's period renews, as RFC 3339, if the provider states one.
    ///
    /// **Absent means not stated, never "does not renew".** Some providers state
    /// a period for a money pool (a monthly workspace credit limit with its own
    /// reset time); others state none for a pool that may well be monthly (paid
    /// overage named `monthly_limit` with no date anywhere in the payload). A
    /// consumer that reads absence as "this balance is all there will ever be"
    /// under-spends at best, and at worst treats a pool that refills on the 1st
    /// as permanently exhausted.
    ///
    /// A producer sets it only from a value the provider sent. A period guessed
    /// from a field name is the fabricated-precision failure this crate refuses
    /// everywhere else.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub resets_at: Option<String>,
}

/// Account labels and subscription information supplied by a provider or vault.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct AccountInfo {
    /// Account email. Absent when the upstream does not identify the account that
    /// way — not a signal about the account itself.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub email: Option<String>,
    /// Organisation label. Absent when the upstream reports none; absent does not
    /// mean a personal account.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub org_name: Option<String>,
    /// The upstream's **own** plan label, not a normalised vocabulary, so it is
    /// not comparable across providers. Display and grouping only. Absent when
    /// the upstream states no plan.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub plan_type: Option<String>,
    /// When the subscription next charges and renews. Stated only for an active
    /// or trialing subscription with no scheduled end; when the upstream states
    /// an end, this is absent and [`Self::subscription_ends_at`] carries it.
    ///
    /// An RFC 3339 date-time, or a full date (`YYYY-MM-DD`) when the upstream
    /// gives only a date. Accept both. A full date means that day, not midnight
    /// UTC, so a producer must not turn one into a timestamp. Absent means the
    /// upstream did not state it, not that the plan never renews.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub subscription_renews_at: Option<String>,
    /// When the subscription is scheduled to end, for example after it was
    /// cancelled. Same format as [`Self::subscription_renews_at`]. Absent means
    /// the upstream stated no end.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub subscription_ends_at: Option<String>,
}

impl AccountInfo {
    pub fn is_empty(&self) -> bool {
        self.email.is_none()
            && self.org_name.is_none()
            && self.plan_type.is_none()
            && self.subscription_renews_at.is_none()
            && self.subscription_ends_at.is_none()
    }
}

/// One saved reset credit and its expiry time.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreditExpiry {
    pub expires_at: String,
}

/// Saved reset credits reported by Codex's read-only credits endpoint.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
#[serde(rename_all = "camelCase")]
pub struct SavedResets {
    #[serde(default)]
    pub available_count: u32,
    /// When the next credit lapses. Absent means **no credit states an expiry**,
    /// which is not the same as none expiring soon.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub soonest_expires_at: Option<String>,
    #[serde(default)]
    pub credits: Vec<CreditExpiry>,
}

fn account_info_is_empty(value: &Option<AccountInfo>) -> bool {
    value.as_ref().map(AccountInfo::is_empty).unwrap_or(true)
}

/// One provider/account's usage entry. The `/usage` response is an array of
/// these. A fetch failure becomes an entry carrying `error` (silent-degrade),
/// never a failure of the whole array.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ProviderUsage {
    /// CodexBar provider name (e.g. "codex"), which consumers map to their own id.
    pub provider: String,
    /// Canonical API provider identifier — the models.dev slug for the same
    /// provider (e.g. "openai" when `provider == "codex"`, "anthropic" for
    /// "claude", "google" for "gemini", "xai" for "grok"). Present when the
    /// producer knows the canonical name; absent for providers with no models.dev
    /// counterpart, where consumers fall back to `provider`. Lets every consumer
    /// key on one canonical name instead of each maintaining its own
    /// CodexBar-name → canonical map.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub api_provider: Option<String>,
    /// The account this entry describes, as the credential store identifies it.
    ///
    /// Absent means the producer **could not resolve an identity for this
    /// credential**, not that the provider has one account. Some credentials
    /// carry no account identity at all (a bare API key), and an entry is also
    /// emitted unlabelled while an identity is still being confirmed — so an
    /// unlabelled entry is not evidence that a labelled one does not exist.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub account: Option<String>,
    /// Which retrieval path produced this (e.g. "oauth") — observability only.
    ///
    /// **Per lane, not per account, and it moves.** One account can be reached
    /// through more than one credential path, and which one answers is decided
    /// per fetch by whichever is healthy. So the same account can report one
    /// value on a poll and another on the next with nothing having changed about
    /// the account, the credential, or anything the consumer did. Do not key on
    /// it, branch on it, or treat a change in it as an event.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub source: Option<String>,
    /// Display labels for the account. Absent when the upstream supplies none of
    /// them; carries no operational meaning.
    #[serde(skip_serializing_if = "account_info_is_empty", default)]
    pub account_info: Option<AccountInfo>,
    /// When this entry's figures were last **successfully** fetched — producer
    /// time, per entry, never a common instant across the array.
    ///
    /// Absent means this credential has never had a successful fetch. It keeps
    /// its old value while a failure is being retried, so it ages honestly rather
    /// than pausing; never restamp it with your own poll time.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub fetched_at: Option<String>,
    /// Banked quota-reset credits held by this account.
    ///
    /// Absent means there is no credit inventory to report — which includes the
    /// inventory lookup having **failed** on this fetch, since it is separate
    /// from the usage fetch and may fail without degrading the entry. Absent is
    /// therefore not "zero credits held".
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub saved_resets: Option<SavedResets>,
    /// The windows. Absent on a degraded entry, and on an entry whose credential
    /// works but whose account reports no quota at all — read `error` and
    /// `error_class` to tell those apart, rather than inferring from this field.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub usage: Option<Usage>,
    /// Prepaid balances and credit pools on this account, when the provider
    /// reports any.
    ///
    /// Deliberately apart from [`Self::usage`], because a pool and a rate window
    /// are different facts that fail in opposite directions: over-consuming a
    /// window gets you throttled and recovers by waiting, while over-consuming a
    /// balance gets you billed and recovers by paying. Nothing in a routing loop
    /// can undo the second, so a balance is never expressed as a window, never
    /// carries a reset, and never appears as a percentage — a consumer that
    /// found one where it expects headroom would pace into a bill.
    ///
    /// Absent means the producer has nothing to say, which is not the same as an
    /// account having no credit. Empty means it looked and the provider reports
    /// no pools.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub spend: Option<Vec<Pool>>,
    /// Present only on a degraded entry. The consumer skips any entry with a
    /// truthy `error`.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    /// A stable, machine-readable name for *why* a degraded entry failed,
    /// published beside the human-readable `error`.
    ///
    /// `error` is prose with no stability promise, so consumers are told not to
    /// branch on it — which leaves them no way to separate failures that mean
    /// something from failures that are a permanent, correct state. A host that
    /// never configured a provider and a host whose credential broke this
    /// morning both produce a degraded entry, and only the second is worth
    /// anyone's attention.
    ///
    /// Classes currently produced:
    ///
    /// | Value | Meaning |
    /// |---|---|
    /// | `credential_absent` | No credential was found. Permanent and correct on a host that never configured this provider; nothing to fix. |
    /// | `credential_unusable` | A credential was found but cannot be used as it stands (empty, incomplete, or refused by the credential store). Someone configured this and it needs fixing. |
    /// | `credential_rejected` | The upstream rejected the credential (401/403). Usually means logging in again. |
    /// | `no_quota_reported` | The credential works and the account genuinely has no quota to report. Not a failure. |
    /// | `upstream_failed` | The upstream could not be reached or returned an error status. Usually transient. |
    /// | `decode_failed` | The response arrived but was not the expected shape. |
    ///
    /// **This list will grow.** A consumer must render an unrecognised class as
    /// a degraded entry with an unknown reason — never drop the entry, and
    /// never fold it into an existing bucket. It is a `String` rather than an
    /// enum for exactly that reason: on an observability surface, meeting an
    /// unknown value must not turn into a parse failure that makes a provider
    /// disappear at the moment its state changed.
    ///
    /// Absent on healthy entries, and absent from any producer that predates
    /// this field.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub error_class: Option<String>,
    /// Present when this entry is a last-known-good reading served through an
    /// ongoing failure, absent when it is a fresh success.
    ///
    /// Without it a preserved reading is byte-identical to a fresh one apart
    /// from `fetched_at`, so a consumer cannot separate "this figure is old
    /// because the producer has been unable to reach the provider" from "this
    /// figure is old because nothing polled recently". Those have opposite
    /// remedies — the first is a reason to stop acting on the number, the
    /// second is not — and a consumer with only a timestamp has to guess with a
    /// wall-clock threshold, which denies fresh-enough data to catch stale data.
    ///
    /// A producer serving preserved readings is behaving correctly: a brief
    /// upstream failure should not blank a window. This field discloses that it
    /// is happening rather than reporting a fault.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub stale: Option<Stale>,
}

/// Why an entry is being served through a failure, and since when.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Stale {
    /// When the producer first failed to refresh this entry, RFC3339.
    ///
    /// Distinct from `fetched_at`, which is when the served reading was
    /// obtained. The gap between them is how long the producer has been unable
    /// to look, which is the quantity a staleness policy actually wants: a
    /// reading can be minutes old with the producer perfectly healthy, or
    /// seconds old with the producer failing since just after it was taken.
    pub since: String,
    /// The failure class, using the same vocabulary as `error_class` on a
    /// degraded entry.
    ///
    /// Carried so a consumer can tell a flapping upstream from a credential
    /// that has started refusing, without branching on prose. Optional because
    /// a producer may preserve a reading for a reason it cannot classify.
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub class: Option<String>,
}

impl ProviderUsage {
    /// A healthy entry with resolved windows.
    pub fn healthy(provider: &str, account: Option<String>, source: &str, usage: Usage) -> Self {
        Self {
            provider: provider.to_string(),
            api_provider: None,
            account,
            source: Some(source.to_string()),
            account_info: None,
            fetched_at: None,
            saved_resets: None,
            usage: Some(usage),
            spend: None,
            error: None,
            error_class: None,
            stale: None,
        }
    }

    /// A degraded entry: the provider is named so the consumer can correlate,
    /// but it carries only an error string and no windows.
    pub fn degraded(provider: &str, error: impl std::fmt::Display) -> Self {
        Self {
            provider: provider.to_string(),
            api_provider: None,
            account: None,
            source: None,
            account_info: None,
            fetched_at: None,
            saved_resets: None,
            usage: None,
            spend: None,
            error: Some(error.to_string()),
            error_class: None,
            stale: None,
        }
    }

    /// A degraded entry that also names *why* it failed.
    ///
    /// Prefer this over [`Self::degraded`] wherever the producer knows the
    /// class: without it a consumer can only tell an unconfigured provider from
    /// a broken one by reading prose it has been told not to parse. See
    /// [`ProviderUsage::error_class`] for the classes and for the rule that an
    /// unrecognised one must still render.
    pub fn degraded_with_class(
        provider: &str,
        error: impl std::fmt::Display,
        error_class: impl Into<String>,
    ) -> Self {
        Self {
            error_class: Some(error_class.into()),
            ..Self::degraded(provider, error)
        }
    }
}

/// Shared account-identity type (commons#13, operator-ratified 2026-08-29).
///
/// `provider` was two namespaces wearing one name across the fleet: usage
/// sources, credential configs, sweep-unit names, and catalog slugs all emit
/// plausible provider strings, so a wrong join never fails loudly. This type
/// makes the namespace explicit so no consumer ever writes the alias map that
/// a silent join failure invites.
///
/// Absence is ordinary here, not exceptional: at ratification time 34 of 40
/// live wire entries carried no verified identity. Consumers must treat a
/// missing `account_ref` as the expected shape, never as an error.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccountIdentity {
    /// Source-scoped provider name, spelled in the emitting module's own
    /// namespace (a usage source's "claude", a registry's invented plan
    /// name). MANDATORY. The same string under two sources need not mean the
    /// same thing; this field never claims cross-source meaning.
    pub provider: String,

    /// The models.dev slug for this provider, when a join to the model
    /// catalog EXISTS. Named for its source deliberately: a second catalog
    /// source would get its own field rather than silently changing this
    /// one's meaning.
    ///
    /// `None` means NO JOIN EXISTS — never fall back to [`Self::provider`].
    /// models.dev publishes no alias metadata of any kind (measured:
    /// no `alias`, `renamed_from`, `supersedes`, or `deprecated_by`), so
    /// there is nothing for a fallback to fall back to; "use the other name"
    /// fabricates a join, and a fabricated join produces plausible rows
    /// forever instead of erroring.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub models_dev_provider_slug: Option<String>,

    /// Verified account identity, when the provider disclosed one.
    ///
    /// PROPAGATION RULE: provenance travels with the value and is NEVER
    /// re-derived downstream. The moment a consumer writes
    /// `if provider == "anthropic" { assume frozen }` they have re-implemented
    /// the producer's discriminant from the outside, keyed on a provider
    /// string — the exact namespace hazard this type exists to prevent,
    /// recreated one level down. The producer that holds the value at its
    /// source knows which branch produced it; nobody else does.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub account_ref: Option<AccountRef>,
}

/// An account identity value bundled with how it was obtained.
///
/// Bundled rather than parallel fields, so the bug-causing states — a value
/// with no provenance, or a provenance with no value — are unrepresentable.
/// Absent identity carries no provenance because there is nothing to qualify.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccountRef {
    /// The identity value as the provider disclosed it (an email, an account
    /// id, an org slug — whatever the source speaks).
    pub value: String,
    /// Which staleness story produced [`Self::value`]. See
    /// [`AccountRefProvenance`] for why the two must never be conflated.
    pub provenance: AccountRefProvenance,
}

/// The two ways an account identity reaches the wire, with opposite staleness
/// properties.
///
/// The discriminant says a value *could* be wrong, not that it *is*: the
/// frozen branch goes stale only if a record is re-pointed without a
/// re-login. A consumer holding a [`Self::StoredLogin`] value knows it needs
/// watching; a [`Self::LiveClaim`] value re-corrects on every read.
///
/// This enum is CLOSED on purpose — identity-bearing, not diagnostic. An
/// unknown provenance must refuse to decode rather than default: a wrong
/// guess here poisons joins silently, which is worse than a loud decode
/// error on a version skew.
///
/// WHAT CLOSURE COSTS: because decoders refuse unknown spellings, adding a
/// variant is a WIRE-BREAKING change for every consumer on an older crate
/// version — a coordinated version boundary across all adopters, never an
/// additive edit. That cost is deliberate. It is also a boundary nobody
/// should be crossing:
///
/// This enum discriminates STALENESS AMONG CUSTODIAN-VOUCHED VALUES. A
/// consumer-derived identity — parsed from a logged-in page, say — is not a
/// third staleness case: it changes WHO VOUCHES, and belongs in its own
/// field rather than as a variant here. [`AccountIdentity::account_ref`]
/// means "the custodian observed this"; a value the custodian did not vouch
/// for does not belong in it at any spelling, because then a provenance tag
/// does load-bearing work AGAINST its own field's semantics — the shape
/// that fails quietly. A separate field is additive and breaks no decoder;
/// a variant here is the coordinated wire boundary AND an unattested value
/// in an attested field — the expensive mistake and the semantic one at
/// once.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccountRefProvenance {
    /// Parsed from the served token at read time — re-derived on every get,
    /// self-correcting.
    LiveClaim,
    /// Stored at login time and returned unchanged — frozen, can go stale
    /// silently if the account is re-pointed without a re-login.
    StoredLogin,
}

#[cfg(test)]
mod tests {
    /// `AccountInfo` IS AN IDENTITY STRUCT, SO AN ADDITIVE FIELD HERE IS A
    /// DECISION RATHER THAN A DETAIL. This pins its serialised key set closed
    /// in both directions so adding one reddens here, at the moment it is
    /// written, rather than at whatever a consumer notices later.
    ///
    /// Why an identity struct earns a pin that an ordinary payload does not:
    /// downstream, `ck quota --redact` renders these fields for screenshots and
    /// removes the identifying ones BY NAME -- so a new identifying field
    /// defaults to visible, the redaction filter keeps passing, its tests keep
    /// going green, and a published screenshot carries a name nobody can
    /// unpublish. Measured live 2026-09-18: Anthropic's `/api/oauth/profile`
    /// carries `account.full_name`, `account.display_name` and
    /// `organization.name` beside the subscription tier a consumer wanted, so
    /// the pressure to widen this struct is real and already arrived once.
    ///
    /// THE THREE-LINK CHAIN, so nobody mistakes one link for the whole:
    /// 1 the capturing consumer decodes only the fields it means to keep,
    /// 2 THIS PIN (the shared struct's key set, closed at the producer),
    /// 3 the rendering consumer's redaction filter, downstream.
    ///
    /// Link 2 alone does not stop a field being added; it stops one being added
    /// WITHOUT SOMEONE SAYING SO. If you are here because this test is red and
    /// the field is genuinely wanted, add it, update this test, and tell the
    /// `ck quota --redact` owner -- that last step is the whole reason the test
    /// exists.
    #[test]
    fn the_account_info_key_set_is_closed_in_both_directions() {
        let populated = AccountInfo {
            email: Some("operator@example.com".to_string()),
            org_name: Some("Example Org".to_string()),
            plan_type: Some("max".to_string()),
            subscription_renews_at: Some("2026-11-03".to_string()),
            subscription_ends_at: Some("2026-12-01T00:00:00Z".to_string()),
        };
        let value = serde_json::to_value(&populated).expect("serialise");
        let object = value.as_object().expect("object");
        let mut keys: Vec<&str> = object.keys().map(String::as_str).collect();
        keys.sort_unstable();
        assert_eq!(
            keys,
            [
                "email",
                "orgName",
                "planType",
                "subscriptionEndsAt",
                "subscriptionRenewsAt"
            ],
            "AccountInfo's serialised key set changed. If a field was ADDED: is it \
             personally identifying? If so it reaches `ck quota --redact`, whose filter \
             removes fields by name and will pass it straight through into a screenshot. \
             Update this test and notify that owner."
        );

        // The other direction: every empty account must serialise to nothing at
        // all, so a consumer cannot read an absent identity as an empty one.
        let empty = serde_json::to_value(AccountInfo::default()).expect("serialise");
        assert_eq!(
            empty.as_object().expect("object").len(),
            0,
            "an empty AccountInfo must emit no keys; a present-but-null field reads to a \
             consumer as a resolved-and-blank identity rather than an unresolved one"
        );
    }

    /// The subscription dates round-trip under their camelCase names, a
    /// date-only value survives byte for byte, and a payload written before
    /// the fields existed still decodes with both absent.
    #[test]
    fn subscription_dates_round_trip_and_older_payloads_still_decode() {
        let info = AccountInfo {
            subscription_renews_at: Some("2026-11-03".to_string()),
            subscription_ends_at: Some("2026-12-01T09:30:00Z".to_string()),
            ..AccountInfo::default()
        };
        let value = serde_json::to_value(&info).expect("serialise");
        assert_eq!(value["subscriptionRenewsAt"], "2026-11-03");
        assert_eq!(value["subscriptionEndsAt"], "2026-12-01T09:30:00Z");
        let back: AccountInfo = serde_json::from_value(value).expect("decode");
        assert_eq!(back, info);

        let older: AccountInfo =
            serde_json::from_str(r#"{"email":"a@example.com","planType":"max"}"#)
                .expect("a payload without the new fields decodes");
        assert_eq!(older.subscription_renews_at, None);
        assert_eq!(older.subscription_ends_at, None);

        let one = AccountInfo {
            subscription_renews_at: Some("2026-11-03".to_string()),
            ..AccountInfo::default()
        };
        let object = serde_json::to_value(&one).expect("serialise");
        assert!(
            object.get("subscriptionEndsAt").is_none(),
            "an unstated end is omitted, not sent as null: {object}"
        );

        // Each date alone makes the account info non-empty, so it is published
        // rather than dropped as an empty label set.
        assert!(!one.is_empty(), "a renewal date alone is not empty");
        let ends_only = AccountInfo {
            subscription_ends_at: Some("2026-12-01".to_string()),
            ..AccountInfo::default()
        };
        assert!(!ends_only.is_empty(), "an end date alone is not empty");
    }

    /// Full AccountIdentity round-trips with every field present, and the
    /// wire spelling of both provenance branches is pinned.
    #[test]
    fn account_identity_full_round_trip_pins_provenance_spellings() {
        let full = AccountIdentity {
            provider: "claude".into(),
            models_dev_provider_slug: Some("anthropic".into()),
            account_ref: Some(AccountRef {
                value: "ufuk@example.com".into(),
                provenance: AccountRefProvenance::StoredLogin,
            }),
        };
        let wire = serde_json::to_string(&full).unwrap();
        assert!(
            wire.contains("\"stored_login\""),
            "frozen branch spelling: {wire}"
        );
        let back: AccountIdentity = serde_json::from_str(&wire).unwrap();
        assert_eq!(back, full);

        let live = serde_json::to_string(&AccountRefProvenance::LiveClaim).unwrap();
        assert_eq!(live, "\"live_claim\"");
    }

    /// A minimal identity (provider only) keeps absent fields ABSENT on the
    /// wire — not null — so pre-identity consumers see byte-identical output.
    #[test]
    fn a_minimal_identity_serializes_provider_only() {
        let min = AccountIdentity {
            provider: "kimi-for-coding".into(),
            models_dev_provider_slug: None,
            account_ref: None,
        };
        let wire = serde_json::to_string(&min).unwrap();
        assert_eq!(wire, r#"{"provider":"kimi-for-coding"}"#);
        let back: AccountIdentity = serde_json::from_str(&wire).unwrap();
        assert_eq!(back, min);
    }

    /// A null slug stays None through a round trip and never inherits the
    /// provider's value: the fallback is fabrication, and this asserts the
    /// bytes, not the intent.
    #[test]
    fn a_none_slug_never_becomes_the_provider_value() {
        let id = AccountIdentity {
            provider: "qwen-cloud".into(),
            models_dev_provider_slug: None,
            account_ref: None,
        };
        let wire = serde_json::to_string(&id).unwrap();
        assert!(
            !wire.contains("models_dev_provider_slug"),
            "absent, not null: {wire}"
        );
        let back: AccountIdentity = serde_json::from_str(&wire).unwrap();
        assert_eq!(back.models_dev_provider_slug, None);
        assert_ne!(
            back.models_dev_provider_slug.as_deref(),
            Some(back.provider.as_str()),
            "a decode path that mirrors provider into the slug has fabricated a join"
        );
    }

    /// Unknown provenance REFUSES to decode. Identity-bearing enum: a
    /// wrong-guess default poisons joins silently, so version skew must fail
    /// loud here, unlike the diagnostic value enums.
    #[test]
    fn an_unknown_provenance_refuses_to_decode() {
        let err = serde_json::from_str::<AccountRef>(r#"{"value":"x","provenance":"vibes"}"#);
        assert!(err.is_err(), "unknown provenance must refuse, got {err:?}");
        // And a bare value with no provenance is unrepresentable:
        let err2 = serde_json::from_str::<AccountRef>(r#"{"value":"x"}"#);
        assert!(
            err2.is_err(),
            "value without provenance must refuse, got {err2:?}"
        );
    }

    /// A window with no stated mechanic serializes exactly as before.
    ///
    /// The additive guarantee: every existing producer omits the field, and a
    /// consumer on the previous version must see byte-identical output.
    #[test]
    fn a_window_without_regeneration_is_unchanged_on_the_wire() {
        let window = RateWindow {
            used_percent: 42.0,
            raw_used_percent: None,
            resets_at: Some("2026-08-17T00:00:00Z".to_string()),
            window_minutes: Some(300),
            used_count: None,
            total_count: None,
            regeneration: None,
            window_kind: None,
            breakdown: None,
        };
        let json = serde_json::to_string(&window).expect("serializes");
        assert!(
            !json.contains("regeneration"),
            "an absent mechanic must not appear on the wire: {json}"
        );
    }

    /// A payload from before this field decodes into the new shape.
    #[test]
    fn a_pre_regeneration_payload_still_decodes() {
        let json = r#"{"usedPercent":42.0,"resetsAt":"2026-08-17T00:00:00Z","windowMinutes":300}"#;
        let window: RateWindow = serde_json::from_str(json).expect("older payloads must decode");
        assert_eq!(window.regeneration, None);
    }

    /// The observed cliff shape round-trips, rate and all.
    ///
    /// SHAPE FROM A LIVE CAPTURE (insula#1, 2026-08-17): a credentialed JetBrains
    /// account stating `tariff: { amount: "1000000", duration: "PT720H" }` beside
    /// a known next-refill instant. First observed rate-bearing payload; the
    /// numbers here are its scrubbed values.
    #[test]
    fn a_stated_cliff_refill_round_trips() {
        let window = RateWindow {
            used_percent: 0.67,
            raw_used_percent: None,
            resets_at: Some("2026-08-15T06:00:00.000Z".to_string()),
            window_minutes: None,
            used_count: Some(8100.0),
            total_count: Some(1_207_000.0),
            regeneration: Some(Regeneration {
                mechanic: "cliff".to_string(),
                rate: Some(RegenerationRate {
                    amount: 1_000_000.0,
                    per_minutes: 43_200,
                }),
            }),
            window_kind: None,
            breakdown: None,
        };
        let json = serde_json::to_string(&window).expect("serializes");
        let back: RateWindow = serde_json::from_str(&json).expect("round-trips");
        assert_eq!(back, window);
        assert!(
            json.contains("\"perMinutes\":43200"),
            "camelCase on the wire: {json}"
        );
    }

    /// A mechanic with no rate is a valid statement.
    ///
    /// "Credits refresh monthly" with no amount is a real upstream shape, and it
    /// is precisely the case a rate-only field could not have expressed — which
    /// is why this is an object rather than a bare rate.
    #[test]
    fn a_mechanic_without_a_rate_is_valid() {
        let json = r#"{"usedPercent":10.0,"regeneration":{"mechanic":"drip"}}"#;
        let window: RateWindow = serde_json::from_str(json).expect("decodes");
        let regeneration = window.regeneration.expect("the mechanic is stated");
        assert_eq!(regeneration.mechanic, "drip");
        assert_eq!(
            regeneration.rate, None,
            "the mechanic stands without a rate"
        );
    }

    /// A mechanic this version has never seen decodes intact.
    ///
    /// THE REASON `mechanic` IS A STRING. On an observability wire a new variant
    /// must not delete the record that reports it: an enum here would fail the
    /// whole entry, so the state a consumer most needs to see is the one that
    /// would vanish. Consumers treat an unrecognised value as `unstated` --
    /// render it, pace on nothing.
    #[test]
    fn an_unrecognised_mechanic_decodes_rather_than_dropping_the_window() {
        let json = r#"{"usedPercent":10.0,"regeneration":{"mechanic":"stepped_thaw"}}"#;
        let window: RateWindow = serde_json::from_str(json).expect("a future variant must decode");
        assert_eq!(
            window.regeneration.expect("present").mechanic,
            "stepped_thaw"
        );
        assert_eq!(window.used_percent, 10.0, "the rest of the window survives");
    }

    /// An entry without the field serializes exactly as before.
    ///
    /// Every consumer decoding today's shape must keep working, so absence has
    /// to be byte-identical rather than merely tolerated.
    #[test]
    fn a_fresh_entry_carries_no_stale_key() {
        let entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        let json = serde_json::to_string(&entry).expect("entry serializes");

        assert!(
            !json.contains("stale"),
            "a fresh entry must not emit the key at all: {json}"
        );
    }

    /// A preserved reading states when the producer stopped being able to look.
    ///
    /// `since` is deliberately not `fetchedAt`: the reading was taken when it
    /// was taken, and the failure began afterwards. The gap between the two is
    /// how long the producer has been blind, which is the quantity a staleness
    /// policy wants -- an entry can be minutes old with the producer healthy,
    /// or seconds old with it failing since just after the read.
    #[test]
    fn a_preserved_reading_states_when_the_failure_began() {
        let mut entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        entry.fetched_at = Some("2026-08-13T10:00:00Z".to_string());
        entry.stale = Some(Stale {
            since: "2026-08-13T10:02:00Z".to_string(),
            class: Some("upstream_failed".to_string()),
        });

        let json = serde_json::to_string(&entry).expect("entry serializes");
        let back: ProviderUsage = serde_json::from_str(&json).expect("entry round-trips");
        let stale = back.stale.expect("the disclosure survives the round trip");

        assert_eq!(stale.since, "2026-08-13T10:02:00Z");
        assert_eq!(stale.class.as_deref(), Some("upstream_failed"));
        assert_ne!(
            Some(stale.since.as_str()),
            back.fetched_at.as_deref(),
            "the two timestamps answer different questions and must not be conflated"
        );
        assert!(
            json.contains("\"stale\""),
            "the key is camelCase on the wire: {json}"
        );
    }

    /// A producer that cannot classify the failure still discloses the state.
    ///
    /// Optional rather than required so a preserved reading is never suppressed
    /// for want of a label -- disclosing "this is stale, cause unstated" beats
    /// looking fresh.
    #[test]
    fn a_disclosure_without_a_class_still_decodes() {
        let json = r#"{"provider":"codex","stale":{"since":"2026-08-13T10:02:00Z"}}"#;
        let entry: ProviderUsage = serde_json::from_str(json).expect("decodes");
        let stale = entry.stale.expect("present");

        assert_eq!(stale.class, None);
        assert_eq!(stale.since, "2026-08-13T10:02:00Z");
    }

    /// An entry from a producer that predates the field decodes unchanged.
    #[test]
    fn an_entry_without_the_field_decodes() {
        let json = r#"{"provider":"codex","source":"oauth"}"#;
        let entry: ProviderUsage = serde_json::from_str(json).expect("decodes");

        assert_eq!(entry.stale, None);
        assert_eq!(entry.provider, "codex");
    }

    use super::*;

    #[test]
    fn account_info_is_omitted_when_empty_and_keeps_partial_labels() {
        let bare = ProviderUsage::healthy(
            "codex",
            None,
            "oauth",
            Usage {
                primary: Some(RateWindow {
                    used_percent: 10.0,
                    raw_used_percent: None,
                    resets_at: None,
                    window_minutes: Some(300),
                    used_count: None,
                    total_count: None,
                    regeneration: None,
                    window_kind: None,
                    breakdown: None,
                }),
                ..Default::default()
            },
        );
        let json = serde_json::to_string(&bare).unwrap();
        assert!(
            !json.contains("accountInfo"),
            "empty accountInfo must be omitted"
        );

        let mut labeled = bare.clone();
        labeled.account_info = Some(AccountInfo {
            email: Some("a@b.com".to_string()),
            org_name: None,
            plan_type: Some("pro".to_string()),
            ..AccountInfo::default()
        });
        let json = serde_json::to_string(&labeled).unwrap();
        assert!(json.contains("\"email\":\"a@b.com\""));
        assert!(json.contains("\"planType\":\"pro\""));
        assert!(!json.contains("orgName"), "absent orgName must be omitted");
    }

    #[test]
    fn saved_resets_use_camel_case_and_round_trip() {
        let entry = ProviderUsage {
            saved_resets: Some(SavedResets {
                available_count: 2,
                soonest_expires_at: Some("2026-07-31T20:11:35Z".to_string()),
                credits: vec![CreditExpiry {
                    expires_at: "2026-07-31T20:11:35Z".to_string(),
                }],
            }),
            ..ProviderUsage::degraded("codex", "x")
        };
        let json = serde_json::to_string(&entry).unwrap();
        assert!(json.contains("\"savedResets\""));
        assert!(json.contains("\"availableCount\":2"));
        assert!(json.contains("\"soonestExpiresAt\""));
        let back: ProviderUsage = serde_json::from_str(&json).unwrap();
        assert_eq!(back, entry);
    }

    #[test]
    fn raw_used_percent_is_absent_from_unrelaxed_windows_and_camel_case_when_present() {
        let unrelaxed = RateWindow {
            used_percent: 41.0,
            raw_used_percent: None,
            resets_at: Some("2026-07-20T00:00:00Z".to_string()),
            window_minutes: Some(10080),
            used_count: None,
            total_count: None,
            regeneration: None,
            window_kind: None,
            breakdown: None,
        };
        let json = serde_json::to_string(&unrelaxed).unwrap();
        assert!(
            !json.contains("rawUsedPercent"),
            "unrelaxed window must not carry the field"
        );

        let relaxed = RateWindow {
            used_percent: 0.0,
            raw_used_percent: Some(70.0),
            resets_at: Some("2026-07-20T00:00:00Z".to_string()),
            window_minutes: Some(10080),
            used_count: None,
            total_count: None,
            regeneration: None,
            window_kind: None,
            breakdown: None,
        };
        let json = serde_json::to_string(&relaxed).unwrap();
        assert!(json.contains("\"rawUsedPercent\":70.0"));
        let back: RateWindow = serde_json::from_str(&json).unwrap();
        assert_eq!(back, relaxed);
    }

    #[test]
    fn healthy_entry_omits_error_and_degraded_entry_omits_usage() {
        let healthy = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        let json = serde_json::to_string(&healthy).unwrap();
        assert!(!json.contains("error"));

        let degraded = ProviderUsage::degraded("codex", "no session");
        let json = serde_json::to_string(&degraded).unwrap();
        assert!(json.contains("\"error\":\"no session\""));
        assert!(!json.contains("usage"));
    }

    #[test]
    fn api_provider_is_camel_case_present_when_set_and_omitted_when_absent() {
        let mut entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        let json = serde_json::to_string(&entry).unwrap();
        assert!(
            !json.contains("apiProvider"),
            "absent api_provider must be omitted"
        );

        entry.api_provider = Some("openai".to_string());
        let json = serde_json::to_string(&entry).unwrap();
        assert!(json.contains("\"apiProvider\":\"openai\""));
        let back: ProviderUsage = serde_json::from_str(&json).unwrap();
        assert_eq!(back, entry);
    }

    #[test]
    fn used_count_and_total_count_are_camel_case_and_omitted_when_absent() {
        let window = RateWindow {
            used_percent: 25.8,
            raw_used_percent: None,
            resets_at: Some("2026-07-26T14:09:00Z".to_string()),
            window_minutes: Some(10080),
            used_count: None,
            total_count: None,
            regeneration: None,
            window_kind: None,
            breakdown: None,
        };
        let json = serde_json::to_string(&window).unwrap();
        assert!(
            !json.contains("usedCount"),
            "absent used_count must be omitted"
        );
        assert!(
            !json.contains("totalCount"),
            "absent total_count must be omitted"
        );

        let enriched = RateWindow {
            used_count: Some(10336.0),
            total_count: Some(40000.0),
            ..window
        };
        let json = serde_json::to_string(&enriched).unwrap();
        assert!(json.contains("\"usedCount\":10336.0"));
        assert!(json.contains("\"totalCount\":40000.0"));
        let back: RateWindow = serde_json::from_str(&json).unwrap();
        assert_eq!(back, enriched);
    }

    /// The field is additive: a producer that does not set it must serialize
    /// exactly as before, or adding it changes every existing entry on the wire.
    #[test]
    fn an_entry_without_a_class_serializes_as_it_did_before_the_field_existed() {
        let entry = ProviderUsage::degraded("codex", "no session: nothing configured");
        let json = serde_json::to_string(&entry).unwrap();

        assert!(!json.contains("errorClass"), "absent class must be omitted");
        // Not vacuous: the entry really is a degraded one carrying its message,
        // so this cannot pass by serializing something empty.
        assert!(json.contains("\"error\":\"no session: nothing configured\""));

        let back: ProviderUsage = serde_json::from_str(&json).unwrap();
        assert_eq!(back, entry);
        assert_eq!(back.error_class, None);
    }

    #[test]
    fn a_class_round_trips_under_its_camel_case_wire_name() {
        let entry = ProviderUsage::degraded_with_class(
            "gemini",
            "credential unusable: gemini creds have no refresh_token",
            "credential_unusable",
        );
        let json = serde_json::to_string(&entry).unwrap();

        assert!(json.contains("\"errorClass\":\"credential_unusable\""));
        let back: ProviderUsage = serde_json::from_str(&json).unwrap();
        assert_eq!(back, entry);
    }

    /// The classes are open by design, so a consumer built today must survive a
    /// producer that ships a class it has never heard of. Modelling the field as
    /// a `String` is what buys that: an enum would make this a parse failure,
    /// and on an observability surface a parse failure means the entry vanishes
    /// at the moment its state changed.
    #[test]
    fn an_unknown_class_decodes_rather_than_failing() {
        let json = r#"{"provider":"someprovider","error":"something new","errorClass":"a_class_from_the_future"}"#;

        let entry: ProviderUsage =
            serde_json::from_str(json).expect("an unrecognised class must not fail to decode");

        assert_eq!(
            entry.error_class.as_deref(),
            Some("a_class_from_the_future")
        );
        // The rest of the entry survives intact, so a consumer can still render
        // it as degraded-with-unknown-reason rather than dropping it.
        assert_eq!(entry.provider, "someprovider");
        assert_eq!(entry.error.as_deref(), Some("something new"));
    }

    /// An entry with no pools serializes exactly as it did before pools existed.
    ///
    /// Consumers pin these payloads, so an additive field that appears as `null`
    /// on every existing entry is not additive in practice. The check is on the
    /// rendered text rather than on the field, because that is what a consumer
    /// parses.
    #[test]
    fn an_entry_without_pools_does_not_mention_them() {
        let entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        let json = serde_json::to_string(&entry).unwrap();
        assert!(!json.contains("spend"), "unexpected spend key: {json}");
    }

    /// Pools survive a round trip, including the two fields a consumer must read
    /// before acting on an amount.
    ///
    /// `basis` and `funding` are what separate "you have 10 granted credits
    /// left" from "you were granted 10 credits and we cannot tell how many
    /// remain". A consumer that loses either one is left with a number it cannot
    /// safely spend against.
    #[test]
    fn pools_round_trip_with_their_basis_and_funding() {
        let pool = Pool {
            id: "granted_balance".to_string(),
            label: "Granted".to_string(),
            funding: PoolFunding::Granted,
            remaining: Some(Amount {
                minor: 1050,
                exponent: 2,
                unit: "CNY".to_string(),
            }),
            total: None,
            basis: PoolBasis::Reported,
            spendable: Some(true),
            resets_at: None,
        };
        let mut entry = ProviderUsage::healthy("deepseek", None, "api", Usage::default());
        entry.spend = Some(vec![pool.clone()]);

        let json = serde_json::to_string(&entry).unwrap();
        let back: ProviderUsage = serde_json::from_str(&json).unwrap();
        assert_eq!(back.spend, Some(vec![pool]));

        // Rendered as the wire spells them, since consumers key on these.
        assert!(json.contains(r#""funding":"granted""#), "{json}");
        assert!(json.contains(r#""basis":"reported""#), "{json}");
        // 10.50 CNY is carried as minor units, never as a float.
        assert!(json.contains(r#""minor":1050"#), "{json}");
        assert!(
            !json.contains("10.5"),
            "an amount was rendered as a decimal: {json}"
        );
    }

    /// A stated reset survives a round trip under its wire name, and an absent
    /// one is not rendered at all.
    #[test]
    fn a_pool_reset_round_trips_and_is_omitted_when_absent() {
        let pool = Pool {
            id: "individual_limit".to_string(),
            label: "Credit limit".to_string(),
            funding: PoolFunding::Unknown,
            remaining: None,
            total: None,
            basis: PoolBasis::Unstated,
            spendable: None,
            resets_at: Some("2026-10-01T00:00:00Z".to_string()),
        };
        let json = serde_json::to_string(&pool).unwrap();
        assert!(
            json.contains(r#""resetsAt":"2026-10-01T00:00:00Z""#),
            "{json}"
        );
        assert_eq!(serde_json::from_str::<Pool>(&json).unwrap(), pool);

        let unstated = Pool {
            resets_at: None,
            ..pool
        };
        let json = serde_json::to_string(&unstated).unwrap();
        assert!(!json.contains("resetsAt"), "{json}");
    }

    /// A pool written before `resetsAt` existed still decodes, to `None`.
    ///
    /// The direction that bites: a producer on an older crate, or a stored
    /// payload, has no such key. A required field would fail every such pool,
    /// and the whole entry with it, rate windows included. serde already decodes
    /// a missing `Option` as `None` (dropping `default` alone leaves this green,
    /// measured), so what this pins is that the field stays optional.
    #[test]
    fn a_pool_without_a_reset_key_decodes_to_none() {
        let json = r#"{ "id": "credits", "label": "Credits", "funding": "unknown", "basis": "reported",
                       "remaining": { "minor": 2402, "exponent": 2, "unit": "credit" } }"#;
        let pool: Pool = serde_json::from_str(json).unwrap();
        assert_eq!(pool.resets_at, None);
    }

    /// An unrecognised funding kind must not take the entry down with it.
    ///
    /// This payload crosses a repository boundary: one project produces it,
    /// others consume it, and their versions move independently. A closed enum
    /// makes the first new funding kind fail deserialization of the WHOLE
    /// `ProviderUsage` entry rather than one field, so an account's rate windows
    /// would vanish because of a credit pool the consumer had never heard of --
    /// and a vanished entry reads as the provider being unavailable.
    ///
    /// Asserted on a mixed entry rather than on the enum alone, because the
    /// blast radius is the point: the usage figure below is what a router acts
    /// on, and it is downstream of the pool that failed.
    #[test]
    fn an_unknown_funding_kind_does_not_discard_the_entry() {
        let json = r#"{
            "provider": "minimax",
            "usage": { "primary": { "usedPercent": 42.0 } },
            "spend": [
              { "id": "a", "label": "A", "funding": "granted",     "basis": "reported" },
              { "id": "b", "label": "B", "funding": "crypto_grant", "basis": "reported" }
            ]
        }"#;

        let entry: ProviderUsage = serde_json::from_str(json).expect("entry must survive");
        let pools = entry.spend.expect("pools present");
        assert_eq!(pools.len(), 2, "no pool may be dropped");
        assert_eq!(pools[0].funding, PoolFunding::Granted);
        // The unrecognised kind lands on Unknown, which is the correct reading:
        // a funding this consumer cannot name is one it must not spend from.
        assert_eq!(pools[1].funding, PoolFunding::Unknown);
        // And the part a router acts on survived.
        assert_eq!(
            entry.usage.and_then(|u| u.primary).map(|w| w.used_percent),
            Some(42.0)
        );
    }

    /// An unrecognised basis reads as unstated, never as exact.
    ///
    /// The two poles are not symmetrical. Treating an exact remainder as a
    /// ceiling under-spends and costs nothing; treating a ceiling as exact
    /// spends money that may not be there. So the fallback folds to the
    /// conservative side, and does so under its own name rather than claiming
    /// the number was derived -- which would assert a fact about a computation
    /// the consumer knows nothing about.
    #[test]
    fn an_unknown_basis_is_unstated_rather_than_exact() {
        let json = r#"{ "id": "a", "label": "A", "funding": "granted",
                        "basis": "sampled_hourly" }"#;
        let pool: Pool = serde_json::from_str(json).expect("pool must survive");
        assert_eq!(pool.basis, PoolBasis::Unstated);
        assert_ne!(
            pool.basis,
            PoolBasis::Reported,
            "an unknown basis must never read as an exact remainder"
        );
    }

    /// A healthy entry must never carry a class: the field's presence is itself
    /// a signal, and a class on a working provider would be a contradiction a
    /// consumer has to resolve.
    #[test]
    fn a_healthy_entry_carries_no_class() {
        let entry = ProviderUsage::healthy("codex", None, "oauth", Usage::default());
        assert_eq!(entry.error_class, None);
        assert!(!serde_json::to_string(&entry)
            .unwrap()
            .contains("errorClass"));
    }

    fn weekly(breakdown: Option<UsageBreakdown>) -> RateWindow {
        RateWindow {
            used_percent: 85.0,
            raw_used_percent: None,
            resets_at: Some("2026-09-30T14:00:00Z".to_string()),
            window_minutes: Some(10_080),
            used_count: None,
            total_count: None,
            regeneration: None,
            window_kind: None,
            breakdown,
        }
    }

    /// `windowKind` is additive: a window that names no period serializes with no
    /// key, so every existing entry is byte-for-byte unchanged.
    #[test]
    fn a_window_naming_no_period_omits_the_kind_key() {
        let json = serde_json::to_string(&weekly(None)).unwrap();
        assert!(!json.contains("windowKind"), "{json}");
    }

    /// A window written before `windowKind` existed still decodes, to `None`.
    #[test]
    fn a_window_from_before_the_kind_decodes_without_one() {
        let json =
            r#"{"usedPercent":85.0,"resetsAt":"2026-09-30T14:00:00Z","windowMinutes":10080}"#;
        let window: RateWindow = serde_json::from_str(json).unwrap();
        assert_eq!(window.window_kind, None);
    }

    /// The case the field exists for: a monthly window with no stated length
    /// carries its kind under the camelCase key and round-trips.
    #[test]
    fn a_monthly_window_with_no_length_carries_its_kind() {
        let window = RateWindow {
            window_minutes: None,
            window_kind: Some(window_kind::MONTHLY.to_string()),
            ..weekly(None)
        };
        let json = serde_json::to_string(&window).unwrap();
        assert!(json.contains(r#""windowKind":"monthly""#), "{json}");
        assert!(!json.contains("windowMinutes"), "{json}");
        let back: RateWindow = serde_json::from_str(&json).unwrap();
        assert_eq!(back, window);
    }

    /// A kind no consumer has seen arrives as itself rather than failing the
    /// decode: the field is an open string, and an enum here would turn one new
    /// period into a response nobody can read.
    #[test]
    fn an_unknown_kind_decodes_as_itself() {
        let json = r#"{"usedPercent":5.0,"windowKind":"fortnightly"}"#;
        let window: RateWindow = serde_json::from_str(json).unwrap();
        assert_eq!(window.window_kind.as_deref(), Some("fortnightly"));
    }

    /// The vocabulary is pinned, so renaming a value (which every consumer
    /// matches as a string) fails here instead of silently on the wire.
    #[test]
    fn the_window_kind_vocabulary_is_pinned() {
        assert_eq!(
            [
                window_kind::HOURLY,
                window_kind::FIVE_HOUR,
                window_kind::DAILY,
                window_kind::WEEKLY,
                window_kind::MONTHLY,
            ],
            ["hourly", "five_hour", "daily", "weekly", "monthly"]
        );
    }

    /// The field is additive: a window without a split serializes exactly as
    /// before, with no `breakdown` key, so every existing entry is unchanged.
    #[test]
    fn a_window_without_a_breakdown_omits_the_key() {
        let json = serde_json::to_string(&weekly(None)).unwrap();
        assert!(!json.contains("breakdown"), "{json}");
    }

    /// A window written before `breakdown` existed still decodes, to `None`.
    #[test]
    fn a_window_from_before_the_field_decodes_without_one() {
        let json =
            r#"{"usedPercent":85.0,"resetsAt":"2026-09-30T14:00:00Z","windowMinutes":10080}"#;
        let window: RateWindow = serde_json::from_str(json).unwrap();
        assert_eq!(window.breakdown, None);
    }

    /// The split round-trips with the upstream's keys verbatim, including one no
    /// consumer has seen, and a row with no figure stays without one rather than
    /// becoming zero.
    #[test]
    fn a_breakdown_round_trips_keys_verbatim_and_keeps_a_missing_share_absent() {
        let breakdown = UsageBreakdown {
            as_of: Some("2026-09-27T08:37:37Z".to_string()),
            window_started_at: Some("2026-09-23T14:00:00Z".to_string()),
            rows: vec![
                BreakdownRow {
                    key: "claude_code".to_string(),
                    share_percent: Some(100.0),
                },
                BreakdownRow {
                    key: "chat".to_string(),
                    share_percent: Some(0.0),
                },
                BreakdownRow {
                    key: "a_surface_from_the_future".to_string(),
                    share_percent: None,
                },
            ],
        };
        let window = weekly(Some(breakdown));
        let json = serde_json::to_string(&window).unwrap();
        assert!(
            json.contains(r#""breakdown":{"asOf":"2026-09-27T08:37:37Z","windowStartedAt":"2026-09-23T14:00:00Z","rows":[{"key":"claude_code","sharePercent":100.0},{"key":"chat","sharePercent":0.0},{"key":"a_surface_from_the_future"}]}"#),
            "{json}"
        );
        let back: RateWindow = serde_json::from_str(&json).unwrap();
        assert_eq!(back, window);
        assert_eq!(back.breakdown.unwrap().rows[2].share_percent, None);
    }
}