Commit dae41eb
authored
fix: detect out-of-range AI Gateway costs instead of wrapping silently (#27602)
Implements:
https://linear.app/codercom/issue/AIGOV-448/use-decimal-for-cost-computation
Follow-up to #26229
Follow-up to the AI Gateway cost-control work. Cost is computed per
token category as `tokens × price / 1_000_000` in `int64`, then summed.
This change makes an unrepresentable result a defined outcome instead of
an accident of integer wrap-around.
## Motivation
The intermediate `tokens × price` can exceed `int64`. Real usage cannot
get there: at a $75/M model the product overflows at roughly 123 billion
tokens in a single response, about six orders of magnitude above a
maxed-out Opus request, so this is not a live incident. The problem is
what happens if it ever does, because the sign of the wrapped value
silently selects between two different failure modes, neither of which
was chosen:
1. **Wraps positive.** A plausible-looking cost is stored, incremented
into the user's daily spend, and enforced against their AI budget. No
error, no signal, wrong number.
2. **Wraps negative.** The value violates `CHECK (cost_micros >= 0)`,
the insert fails, the surrounding transaction rolls back, and
`RecordTokenUsage` returns a Postgres constraint error that says nothing
about overflow. The token usage record is lost entirely, along with its
token counts.
So the same class of bad input either corrupts budget accounting or
discards an audit record, depending on arithmetic that nobody reasoned
about. That is the undefined behaviour.
## Decision
**An unrepresentable cost is treated as bad input, not a large bill.**
Since real usage cannot produce one, it can only mean a wrong price row
or implausible provider-reported token counts. In both cases the true
cost is unknowable, so no number is stored.
**Detect rather than avoid.** `computeCost` now evaluates in `decimal`,
so nothing wraps, and range-checks the total against `[0, MaxInt64]`
before converting back. Out of range returns `errCostOutOfRange`.
Rejecting negatives in the same check also keeps them away from the
non-negative column constraint, which would otherwise discard the
record.
**Log, do not block.** The error is swallowed at the call site: the
record is written with token counts intact and `cost_micros` NULL, the
spend update is skipped, and the condition is logged at ERROR.
**Per-category truncation is unchanged.** Each category is still
truncated independently rather than the total being rounded once, so a
per-category breakdown recomputed from the snapshotted price columns
sums exactly to the stored total. Every existing `computeCost` test case
passes unmodified.1 parent 9dcb75c commit dae41eb
4 files changed
Lines changed: 315 additions & 20 deletions
File tree
- coderd/aibridgedserver
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
347 | 347 | | |
348 | 348 | | |
349 | 349 | | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
350 | 361 | | |
351 | 362 | | |
352 | 363 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
1912 | 1913 | | |
1913 | 1914 | | |
1914 | 1915 | | |
| 1916 | + | |
| 1917 | + | |
| 1918 | + | |
| 1919 | + | |
| 1920 | + | |
| 1921 | + | |
| 1922 | + | |
| 1923 | + | |
| 1924 | + | |
| 1925 | + | |
| 1926 | + | |
| 1927 | + | |
| 1928 | + | |
| 1929 | + | |
| 1930 | + | |
| 1931 | + | |
| 1932 | + | |
| 1933 | + | |
| 1934 | + | |
| 1935 | + | |
| 1936 | + | |
| 1937 | + | |
| 1938 | + | |
| 1939 | + | |
| 1940 | + | |
| 1941 | + | |
| 1942 | + | |
| 1943 | + | |
| 1944 | + | |
| 1945 | + | |
| 1946 | + | |
| 1947 | + | |
| 1948 | + | |
| 1949 | + | |
| 1950 | + | |
| 1951 | + | |
| 1952 | + | |
| 1953 | + | |
| 1954 | + | |
| 1955 | + | |
| 1956 | + | |
| 1957 | + | |
| 1958 | + | |
| 1959 | + | |
| 1960 | + | |
| 1961 | + | |
| 1962 | + | |
| 1963 | + | |
| 1964 | + | |
| 1965 | + | |
| 1966 | + | |
| 1967 | + | |
| 1968 | + | |
| 1969 | + | |
| 1970 | + | |
| 1971 | + | |
| 1972 | + | |
| 1973 | + | |
| 1974 | + | |
| 1975 | + | |
| 1976 | + | |
| 1977 | + | |
| 1978 | + | |
| 1979 | + | |
| 1980 | + | |
| 1981 | + | |
| 1982 | + | |
| 1983 | + | |
| 1984 | + | |
1915 | 1985 | | |
1916 | 1986 | | |
1917 | 1987 | | |
| |||
3534 | 3604 | | |
3535 | 3605 | | |
3536 | 3606 | | |
| 3607 | + | |
| 3608 | + | |
| 3609 | + | |
| 3610 | + | |
3537 | 3611 | | |
3538 | 3612 | | |
3539 | 3613 | | |
| |||
3551 | 3625 | | |
3552 | 3626 | | |
3553 | 3627 | | |
| 3628 | + | |
| 3629 | + | |
| 3630 | + | |
3554 | 3631 | | |
3555 | 3632 | | |
3556 | 3633 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
10 | 11 | | |
11 | 12 | | |
| |||
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
18 | | - | |
19 | | - | |
20 | | - | |
| 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 | + | |
21 | 59 | | |
22 | 60 | | |
23 | 61 | | |
| |||
34 | 72 | | |
35 | 73 | | |
36 | 74 | | |
37 | | - | |
| 75 | + | |
38 | 76 | | |
39 | 77 | | |
40 | | - | |
41 | | - | |
| 78 | + | |
| 79 | + | |
42 | 80 | | |
43 | 81 | | |
44 | 82 | | |
| |||
104 | 142 | | |
105 | 143 | | |
106 | 144 | | |
107 | | - | |
108 | | - | |
109 | | - | |
110 | | - | |
111 | | - | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
112 | 162 | | |
| 163 | + | |
113 | 164 | | |
114 | 165 | | |
115 | 166 | | |
116 | 167 | | |
117 | 168 | | |
118 | 169 | | |
119 | 170 | | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
125 | 192 | | |
126 | 193 | | |
127 | 194 | | |
128 | | - | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
129 | 199 | | |
130 | | - | |
| 200 | + | |
131 | 201 | | |
132 | | - | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
133 | 206 | | |
0 commit comments