cctally 1.113.0 → 1.115.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +4 -6
- package/bin/_cctally_account.py +32 -8
- package/bin/_cctally_cache.py +6035 -722
- package/bin/_cctally_completion.py +1287 -0
- package/bin/_cctally_config.py +37 -0
- package/bin/_cctally_core.py +400 -2
- package/bin/_cctally_dashboard.py +98 -20
- package/bin/_cctally_dashboard_conversation.py +292 -23
- package/bin/_cctally_dashboard_envelope.py +16 -3
- package/bin/_cctally_dashboard_perf.py +128 -9
- package/bin/_cctally_dashboard_sources.py +285 -213
- package/bin/_cctally_db.py +1177 -23
- package/bin/_cctally_diagnosis_sources.py +22 -4
- package/bin/_cctally_doctor.py +572 -45
- package/bin/_cctally_five_hour.py +84 -33
- package/bin/_cctally_forecast.py +108 -15
- package/bin/_cctally_journal.py +3950 -116
- package/bin/_cctally_journal_repair.py +101 -12
- package/bin/_cctally_milestone_history.py +82 -9
- package/bin/_cctally_parser.py +42 -0
- package/bin/_cctally_percent_breakdown.py +3 -2
- package/bin/_cctally_project.py +8 -2
- package/bin/_cctally_quarantine.py +652 -0
- package/bin/_cctally_quota.py +227 -37
- package/bin/_cctally_quota_model.py +360 -83
- package/bin/_cctally_record.py +691 -149
- package/bin/_cctally_rederive.py +320 -41
- package/bin/_cctally_refresh.py +84 -13
- package/bin/_cctally_source_analytics.py +83 -17
- package/bin/_cctally_statusline.py +6154 -517
- package/bin/_cctally_store.py +367 -53
- package/bin/_cctally_transcript.py +8 -7
- package/bin/_cctally_tui.py +61 -56
- package/bin/_cctally_weekrefs.py +128 -21
- package/bin/_lib_account_binding.py +152 -0
- package/bin/_lib_account_protocol.py +386 -0
- package/bin/_lib_aggregators.py +4 -0
- package/bin/_lib_cache_coverage.py +13 -1
- package/bin/_lib_cache_report.py +35 -6
- package/bin/_lib_cache_writer_lock.py +19 -3
- package/bin/_lib_clone_copy.py +142 -0
- package/bin/_lib_codex_conversation.py +32 -0
- package/bin/_lib_codex_conversation_query.py +297 -49
- package/bin/_lib_codex_conversation_watch.py +59 -14
- package/bin/_lib_codex_drain_admission.py +214 -0
- package/bin/_lib_codex_metadata.py +18 -1
- package/bin/_lib_codex_publication_barrier.py +277 -0
- package/bin/_lib_codex_rollout_container.py +502 -0
- package/bin/_lib_conversation_dispatch.py +3 -0
- package/bin/_lib_conversation_query.py +106 -59
- package/bin/_lib_conversation_retention.py +1605 -640
- package/bin/_lib_cost_provenance.py +53 -2
- package/bin/_lib_detector_access_registry.py +3817 -0
- package/bin/_lib_doctor.py +714 -69
- package/bin/_lib_five_hour.py +126 -0
- package/bin/_lib_fmt.py +11 -0
- package/bin/_lib_ingest_frontier.py +414 -20
- package/bin/_lib_journal.py +141 -10
- package/bin/_lib_jsonl.py +5 -4
- package/bin/_lib_legacy_spool.py +126 -0
- package/bin/_lib_legacy_writer_evidence.py +136 -0
- package/bin/_lib_merge_gate.py +231 -86
- package/bin/_lib_meter_rate_change.py +10 -0
- package/bin/_lib_outcome_quarantine.py +690 -0
- package/bin/_lib_perf.py +27 -1
- package/bin/_lib_pricing.py +199 -9
- package/bin/_lib_pricing_check.py +20 -4
- package/bin/_lib_quarantine.py +154 -0
- package/bin/_lib_quota_calibration.py +53 -8
- package/bin/_lib_quota_copy.py +2 -1
- package/bin/_lib_quota_model.py +288 -46
- package/bin/_lib_reclaim_planner.py +792 -0
- package/bin/_lib_rederive.py +319 -82
- package/bin/_lib_segment_summary.py +35 -2
- package/bin/_lib_snapshot_cache.py +21 -0
- package/bin/_lib_sqlite_close.py +81 -0
- package/bin/_lib_statusline_candidates.py +292 -23
- package/bin/_lib_subscription_weeks.py +100 -17
- package/bin/_lib_tick_stats.py +217 -17
- package/bin/_lib_wal_checkpoint.py +1077 -0
- package/bin/_lib_write_budget.py +483 -0
- package/bin/_lib_write_io.py +703 -0
- package/bin/cctally +31 -1
- package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
- package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
- package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
- package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
- package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
- package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
- package/dashboard/static/assets/index-BKITk1CT.css +1 -0
- package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
- package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
- package/dashboard/static/dashboard.html +2 -2
- package/package.json +19 -1
- package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
- package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
package/bin/_lib_perf.py
CHANGED
|
@@ -149,7 +149,8 @@ def _stack():
|
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
class Phase:
|
|
152
|
-
__slots__ = ("name", "elapsed_ms", "count", "meta", "children", "_start",
|
|
152
|
+
__slots__ = ("name", "elapsed_ms", "count", "meta", "children", "_start",
|
|
153
|
+
"_stack", "write_bytes", "_write_start")
|
|
153
154
|
|
|
154
155
|
def __init__(self, name, stack):
|
|
155
156
|
self.name = name
|
|
@@ -159,6 +160,8 @@ class Phase:
|
|
|
159
160
|
self.children = []
|
|
160
161
|
self._start = 0.0
|
|
161
162
|
self._stack = stack
|
|
163
|
+
self.write_bytes = None
|
|
164
|
+
self._write_start = None
|
|
162
165
|
|
|
163
166
|
def set_count(self, n):
|
|
164
167
|
self.count = int(n)
|
|
@@ -169,12 +172,17 @@ class Phase:
|
|
|
169
172
|
self.meta.update(kw)
|
|
170
173
|
|
|
171
174
|
def __enter__(self):
|
|
175
|
+
self._write_start = _write_counter_value()
|
|
172
176
|
self._start = time.perf_counter()
|
|
173
177
|
self._stack.append(self)
|
|
174
178
|
return self
|
|
175
179
|
|
|
176
180
|
def __exit__(self, *exc):
|
|
177
181
|
self.elapsed_ms = (time.perf_counter() - self._start) * 1000.0
|
|
182
|
+
end = _write_counter_value()
|
|
183
|
+
if (self._write_start is not None and end is not None
|
|
184
|
+
and end >= self._write_start):
|
|
185
|
+
self.write_bytes = end - self._write_start
|
|
178
186
|
# Identity-aware unwind. If a nested phase leaked (its __exit__ was
|
|
179
187
|
# skipped — e.g. an exception escaped a manually CM-bracketed region),
|
|
180
188
|
# drop the leaked frames sitting above us so we never append a phase to
|
|
@@ -203,6 +211,8 @@ class Phase:
|
|
|
203
211
|
d["count"] = self.count
|
|
204
212
|
if self.meta:
|
|
205
213
|
d["meta"] = dict(self.meta)
|
|
214
|
+
if self.write_bytes is not None:
|
|
215
|
+
d["write_bytes"] = self.write_bytes
|
|
206
216
|
if self.children:
|
|
207
217
|
d["children"] = [c.to_dict() for c in self.children]
|
|
208
218
|
return d
|
|
@@ -227,6 +237,22 @@ class _NullPhase:
|
|
|
227
237
|
_NULL_PHASE = _NullPhase()
|
|
228
238
|
|
|
229
239
|
|
|
240
|
+
def _write_counter_value():
|
|
241
|
+
"""The process write counter for an armed phase (#901 §5.5), or None.
|
|
242
|
+
|
|
243
|
+
Deep-phase samples are optional: they ride an armed trace only, and the
|
|
244
|
+
always-on totals never depend on them. The counter module is used only
|
|
245
|
+
when the process already loaded it, so this stays stdlib-only."""
|
|
246
|
+
module = sys.modules.get("_lib_write_io")
|
|
247
|
+
if module is None:
|
|
248
|
+
return None
|
|
249
|
+
try:
|
|
250
|
+
reading = module.read_counter()
|
|
251
|
+
except Exception: # noqa: BLE001 — a diagnostic must not raise
|
|
252
|
+
return None
|
|
253
|
+
return reading.value if reading.status == "ok" else None
|
|
254
|
+
|
|
255
|
+
|
|
230
256
|
def phase(name):
|
|
231
257
|
# Per-root capture (#583 S1 §2.2). `_ENABLED` is process-global and HTTP
|
|
232
258
|
# handlers open their own roots on their own threads, so a flip landing
|
package/bin/_lib_pricing.py
CHANGED
|
@@ -19,6 +19,7 @@ from __future__ import annotations
|
|
|
19
19
|
|
|
20
20
|
import dataclasses
|
|
21
21
|
import datetime as dt
|
|
22
|
+
import math
|
|
22
23
|
import re
|
|
23
24
|
import sys
|
|
24
25
|
from typing import Any
|
|
@@ -30,6 +31,34 @@ def _eprint(*args: Any) -> None:
|
|
|
30
31
|
|
|
31
32
|
TIERED_THRESHOLD = 200_000
|
|
32
33
|
|
|
34
|
+
# #929: Claude Haiku 5.5 is priced by prompt length. A card that declares
|
|
35
|
+
# `whole_request_prompt_threshold_tokens` selects ONE card per request from its
|
|
36
|
+
# prompt (input + flat cache creation + cache read): over the threshold every
|
|
37
|
+
# class of that request, output included, bills at the `_above_100k_tokens`
|
|
38
|
+
# rates. This is a different mechanism from the per-class MARGINAL
|
|
39
|
+
# `_above_200k_tokens` split older Sonnet cards carry, and a card never mixes
|
|
40
|
+
# the two. A legacy card carries neither the marker nor any 100K field.
|
|
41
|
+
CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY = "whole_request_prompt_threshold_tokens"
|
|
42
|
+
CLAUDE_WHOLE_REQUEST_THRESHOLD = 100_000
|
|
43
|
+
CLAUDE_WHOLE_REQUEST_TIER_FIELDS = (
|
|
44
|
+
("input_cost_per_token", "input_cost_per_token_above_100k_tokens"),
|
|
45
|
+
("output_cost_per_token", "output_cost_per_token_above_100k_tokens"),
|
|
46
|
+
("cache_creation_input_token_cost",
|
|
47
|
+
"cache_creation_input_token_cost_above_100k_tokens"),
|
|
48
|
+
("cache_read_input_token_cost",
|
|
49
|
+
"cache_read_input_token_cost_above_100k_tokens"),
|
|
50
|
+
)
|
|
51
|
+
CLAUDE_WHOLE_REQUEST_TIER_SUFFIX = "_above_100k_tokens"
|
|
52
|
+
# The marker plus the four recognised tier fields. A card sharing none of them
|
|
53
|
+
# is legacy; the hot path decides that with one `isdisjoint` call. A card that
|
|
54
|
+
# carries some OTHER `_above_100k_tokens` field is malformed: the selector
|
|
55
|
+
# refuses it when the card is otherwise whole-request, and
|
|
56
|
+
# `check_table_shapes` refuses it on any card (`claude_whole_request_
|
|
57
|
+
# declaration_problems`), so it can never silently price at the base card.
|
|
58
|
+
_CLAUDE_WHOLE_REQUEST_KEYS = frozenset(
|
|
59
|
+
(CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY,
|
|
60
|
+
*(tier for _, tier in CLAUDE_WHOLE_REQUEST_TIER_FIELDS)))
|
|
61
|
+
|
|
33
62
|
|
|
34
63
|
def _chip_for_model(name: str) -> str:
|
|
35
64
|
"""Bucket a canonical model id into a small chip palette.
|
|
@@ -63,7 +92,12 @@ def _chip_for_model(name: str) -> str:
|
|
|
63
92
|
# fingerprint a store recorded and refuses a write from an older process. That
|
|
64
93
|
# comparison is day-granular by construction, so two revisions sharing a date
|
|
65
94
|
# compare equal and the older process is authorized to write.
|
|
66
|
-
|
|
95
|
+
#
|
|
96
|
+
# A sync of this date or of either table also changes the dynamic-SQL shape
|
|
97
|
+
# pinned in `bin/_lib_detector_access_registry.py`, so its registry test fails
|
|
98
|
+
# until the affected pins are re-pinned through the reviewed procedure in that
|
|
99
|
+
# module's docstring. Bulk regeneration of the pins alone is not acceptance.
|
|
100
|
+
PRICING_SNAPSHOT_DATE = "2026-10-14"
|
|
67
101
|
PRICING_STALENESS_DAYS = 60 # release pre-flight WARNs past this age
|
|
68
102
|
|
|
69
103
|
|
|
@@ -517,6 +551,21 @@ CLAUDE_MODEL_PRICING: dict[str, dict[str, Any]] = {
|
|
|
517
551
|
"cache_creation_input_token_cost": 1.25e-06,
|
|
518
552
|
"cache_read_input_token_cost": 1e-07,
|
|
519
553
|
},
|
|
554
|
+
"claude-haiku-5-5": {
|
|
555
|
+
# Source: https://platform.claude.com/docs/en/models/haiku-5-5/overview
|
|
556
|
+
# Priced by prompt length (#929): over 100,000 prompt tokens the WHOLE
|
|
557
|
+
# request bills at the _above_100k_tokens card. The 1h write stays
|
|
558
|
+
# derived (selected input x CACHE_WRITE_1H_MULTIPLIER).
|
|
559
|
+
"input_cost_per_token": 1e-07,
|
|
560
|
+
"output_cost_per_token": 5e-07,
|
|
561
|
+
"cache_creation_input_token_cost": 1.25e-07,
|
|
562
|
+
"cache_read_input_token_cost": 1e-08,
|
|
563
|
+
"input_cost_per_token_above_100k_tokens": 5e-07,
|
|
564
|
+
"output_cost_per_token_above_100k_tokens": 2.5e-06,
|
|
565
|
+
"cache_creation_input_token_cost_above_100k_tokens": 6.25e-07,
|
|
566
|
+
"cache_read_input_token_cost_above_100k_tokens": 5e-08,
|
|
567
|
+
"whole_request_prompt_threshold_tokens": 100_000,
|
|
568
|
+
},
|
|
520
569
|
"claude-mythos-5": {
|
|
521
570
|
"input_cost_per_token": 1e-05,
|
|
522
571
|
"output_cost_per_token": 5e-05,
|
|
@@ -656,7 +705,7 @@ CLAUDE_MODEL_PRICING: dict[str, dict[str, Any]] = {
|
|
|
656
705
|
"input_cost_per_token": 2e-06,
|
|
657
706
|
"output_cost_per_token": 1e-05,
|
|
658
707
|
"cache_creation_input_token_cost": 2.5e-06,
|
|
659
|
-
"cache_read_input_token_cost":
|
|
708
|
+
"cache_read_input_token_cost": 1e-07,
|
|
660
709
|
},
|
|
661
710
|
}
|
|
662
711
|
|
|
@@ -1274,6 +1323,118 @@ def claude_usage_dict(*, cache_1h_tokens, speed, input_tokens=0, output_tokens=0
|
|
|
1274
1323
|
return usage
|
|
1275
1324
|
|
|
1276
1325
|
|
|
1326
|
+
def _finite_positive_rate(value) -> bool:
|
|
1327
|
+
return (isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
1328
|
+
and math.isfinite(value) and value > 0)
|
|
1329
|
+
|
|
1330
|
+
|
|
1331
|
+
def _is_whole_request_card(card) -> bool:
|
|
1332
|
+
"""True when ``card`` declares (or half-declares) a whole-request tier:
|
|
1333
|
+
it carries the marker or one of the four recognised tier fields. This is
|
|
1334
|
+
on every Claude request's pricing path, so it is one set operation; the
|
|
1335
|
+
scan for unrecognised ``_above_100k_tokens`` fields lives in
|
|
1336
|
+
``claude_whole_request_declaration_problems``."""
|
|
1337
|
+
return not _CLAUDE_WHOLE_REQUEST_KEYS.isdisjoint(card)
|
|
1338
|
+
|
|
1339
|
+
|
|
1340
|
+
def _unrecognised_whole_request_fields(card) -> list:
|
|
1341
|
+
return sorted(k for k in card
|
|
1342
|
+
if k.endswith(CLAUDE_WHOLE_REQUEST_TIER_SUFFIX)
|
|
1343
|
+
and k not in _CLAUDE_WHOLE_REQUEST_KEYS)
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
def claude_whole_request_declaration_problems(card) -> list:
|
|
1347
|
+
"""Every reason ``card``'s whole-request declaration is malformed (#929).
|
|
1348
|
+
|
|
1349
|
+
``[]`` for a legacy card (neither the marker nor any
|
|
1350
|
+
``_above_100k_tokens`` field) and for a well-formed declaration. Shared by
|
|
1351
|
+
the selector and ``check_table_shapes`` so the two can never disagree
|
|
1352
|
+
about what is well formed. Any ``_above_100k_tokens`` field other than the
|
|
1353
|
+
four recognised ones is a problem, with or without the marker.
|
|
1354
|
+
"""
|
|
1355
|
+
unrecognised = _unrecognised_whole_request_fields(card)
|
|
1356
|
+
if not unrecognised and not _is_whole_request_card(card):
|
|
1357
|
+
return []
|
|
1358
|
+
problems = []
|
|
1359
|
+
key = CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY
|
|
1360
|
+
threshold = card.get(key)
|
|
1361
|
+
if key not in card:
|
|
1362
|
+
problems.append(f"missing {key}")
|
|
1363
|
+
elif (isinstance(threshold, bool) or not isinstance(threshold, int)
|
|
1364
|
+
or threshold != CLAUDE_WHOLE_REQUEST_THRESHOLD):
|
|
1365
|
+
problems.append(
|
|
1366
|
+
f"{key} must be the integer {CLAUDE_WHOLE_REQUEST_THRESHOLD} the "
|
|
1367
|
+
f"_above_100k_tokens fields name (got {threshold!r})")
|
|
1368
|
+
for _, tier in CLAUDE_WHOLE_REQUEST_TIER_FIELDS:
|
|
1369
|
+
if tier not in card:
|
|
1370
|
+
problems.append(f"missing {tier}")
|
|
1371
|
+
elif not _finite_positive_rate(card[tier]):
|
|
1372
|
+
problems.append(
|
|
1373
|
+
f"{tier} not a finite positive number ({card[tier]!r})")
|
|
1374
|
+
mixed = sorted(k for k in card if k.endswith("_above_200k_tokens"))
|
|
1375
|
+
if mixed:
|
|
1376
|
+
problems.append("mixes whole-request and marginal tiers: "
|
|
1377
|
+
+ ", ".join(mixed))
|
|
1378
|
+
if unrecognised:
|
|
1379
|
+
problems.append("unrecognised _above_100k_tokens fields: "
|
|
1380
|
+
+ ", ".join(unrecognised))
|
|
1381
|
+
return problems
|
|
1382
|
+
|
|
1383
|
+
|
|
1384
|
+
def _select_claude_request_card(card, *, input_tokens, cache_creation_tokens,
|
|
1385
|
+
cache_read_tokens, prompt_tokens_for_tier=None):
|
|
1386
|
+
"""The card that prices ONE request (#929). Pure: never writes ``card``.
|
|
1387
|
+
|
|
1388
|
+
A legacy card (neither the marker nor any of the four recognised tier
|
|
1389
|
+
fields) is returned unchanged (the same object) without reading a count.
|
|
1390
|
+
A whole-request card must be well formed, which includes carrying no
|
|
1391
|
+
unrecognised ``_above_100k_tokens`` field, or this raises
|
|
1392
|
+
``ValueError``. Its prompt is ``prompt_tokens_for_tier`` when given (a
|
|
1393
|
+
caller pricing a synthetic subset of a real request), else input + FLAT
|
|
1394
|
+
cache creation + cache read; output and the 1h sub-split never count. At or
|
|
1395
|
+
under the threshold the stored card itself is returned; over it, a NEW dict
|
|
1396
|
+
whose four base slots hold the higher rates and which carries neither the
|
|
1397
|
+
marker nor any tier field.
|
|
1398
|
+
"""
|
|
1399
|
+
if not _is_whole_request_card(card):
|
|
1400
|
+
return card
|
|
1401
|
+
problems = claude_whole_request_declaration_problems(card)
|
|
1402
|
+
if problems:
|
|
1403
|
+
raise ValueError("malformed whole-request pricing declaration: "
|
|
1404
|
+
+ "; ".join(problems))
|
|
1405
|
+
if prompt_tokens_for_tier is None:
|
|
1406
|
+
prompt = ((input_tokens or 0) + (cache_creation_tokens or 0)
|
|
1407
|
+
+ (cache_read_tokens or 0))
|
|
1408
|
+
else:
|
|
1409
|
+
prompt = prompt_tokens_for_tier
|
|
1410
|
+
if prompt <= card[CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY]:
|
|
1411
|
+
return card
|
|
1412
|
+
# No marker and no `_above_100k_tokens` field survives into the selected
|
|
1413
|
+
# card (a well-formed card carries no unrecognised one, but the filter
|
|
1414
|
+
# does not rely on that).
|
|
1415
|
+
selected = {k: v for k, v in card.items()
|
|
1416
|
+
if k != CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY
|
|
1417
|
+
and not k.endswith(CLAUDE_WHOLE_REQUEST_TIER_SUFFIX)}
|
|
1418
|
+
for base, tier in CLAUDE_WHOLE_REQUEST_TIER_FIELDS:
|
|
1419
|
+
selected[base] = card[tier]
|
|
1420
|
+
return selected
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
def _whole_request_cache_create_cost(card, flat, h_raw) -> float:
|
|
1424
|
+
"""Cache-creation USD on a whole-request card: direct flat arithmetic
|
|
1425
|
+
(#929 spec §5), never the legacy 200K band partition, which is not
|
|
1426
|
+
float-identical to it."""
|
|
1427
|
+
if flat <= 0:
|
|
1428
|
+
return 0.0
|
|
1429
|
+
rate_5m = card.get("cache_creation_input_token_cost", 0.0)
|
|
1430
|
+
h = 0 if h_raw is None else max(0, min(int(h_raw), flat))
|
|
1431
|
+
if h == 0:
|
|
1432
|
+
return flat * rate_5m
|
|
1433
|
+
rate_1h = (card.get("input_cost_per_token", 0.0)
|
|
1434
|
+
* current_pricing_snapshot().cache_write_1h_multiplier)
|
|
1435
|
+
return (flat - h) * rate_5m + h * rate_1h
|
|
1436
|
+
|
|
1437
|
+
|
|
1277
1438
|
def _cache_create_cost(pricing: dict, flat: int, h_raw, tiered) -> float:
|
|
1278
1439
|
"""USD for one entry's cache-CREATION tokens, priced by TTL (#195).
|
|
1279
1440
|
|
|
@@ -1320,8 +1481,15 @@ def _calculate_entry_cost(
|
|
|
1320
1481
|
usage: dict[str, Any],
|
|
1321
1482
|
mode: str = "auto",
|
|
1322
1483
|
cost_usd: float | None = None,
|
|
1484
|
+
*,
|
|
1485
|
+
prompt_tokens_for_tier: int | None = None,
|
|
1323
1486
|
) -> float:
|
|
1324
|
-
"""Calculate USD cost for a single API call entry.
|
|
1487
|
+
"""Calculate USD cost for a single API call entry.
|
|
1488
|
+
|
|
1489
|
+
``prompt_tokens_for_tier`` only feeds whole-request card selection (#929):
|
|
1490
|
+
a caller pricing a synthetic subset of a real request passes that
|
|
1491
|
+
request's real prompt total so the subset bills at the request's card.
|
|
1492
|
+
"""
|
|
1325
1493
|
if mode == "display":
|
|
1326
1494
|
return cost_usd if cost_usd is not None else 0.0
|
|
1327
1495
|
if mode == "auto" and cost_usd is not None:
|
|
@@ -1330,6 +1498,18 @@ def _calculate_entry_cost(
|
|
|
1330
1498
|
pricing = _resolve_model_pricing(model)
|
|
1331
1499
|
if pricing is None:
|
|
1332
1500
|
return 0.0
|
|
1501
|
+
# #929: a whole-request card selects ONE card for this request. A legacy
|
|
1502
|
+
# card skips selection entirely and runs the pre-#929 expressions below
|
|
1503
|
+
# byte-for-byte (the selector would return it unchanged anyway).
|
|
1504
|
+
whole_request = _is_whole_request_card(pricing)
|
|
1505
|
+
if whole_request:
|
|
1506
|
+
pricing = _select_claude_request_card(
|
|
1507
|
+
pricing,
|
|
1508
|
+
input_tokens=usage.get("input_tokens", 0),
|
|
1509
|
+
cache_creation_tokens=usage.get("cache_creation_input_tokens", 0),
|
|
1510
|
+
cache_read_tokens=usage.get("cache_read_input_tokens", 0),
|
|
1511
|
+
prompt_tokens_for_tier=prompt_tokens_for_tier,
|
|
1512
|
+
)
|
|
1333
1513
|
|
|
1334
1514
|
def _tiered(tokens: int, base_key: str, tiered_key: str) -> float:
|
|
1335
1515
|
base_rate = pricing.get(base_key, 0.0)
|
|
@@ -1361,12 +1541,22 @@ def _calculate_entry_cost(
|
|
|
1361
1541
|
# arithmetic or comparison, and the `h_raw` side does its own `int()`
|
|
1362
1542
|
# inside the clamp (which IS load-bearing there: `min(int(h_raw), flat)`
|
|
1363
1543
|
# is what pins the 1h portion to whole tokens).
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1544
|
+
if not whole_request:
|
|
1545
|
+
cache_create_cost = _cache_create_cost(
|
|
1546
|
+
pricing,
|
|
1547
|
+
usage.get("cache_creation_input_tokens", 0),
|
|
1548
|
+
usage.get("cache_creation_1h_input_tokens"),
|
|
1549
|
+
_tiered,
|
|
1550
|
+
)
|
|
1551
|
+
else:
|
|
1552
|
+
# The selected card has no `_above_200k_tokens` key, so `_tiered`
|
|
1553
|
+
# prices input, output and cache read as tokens x selected rate; cache
|
|
1554
|
+
# creation takes the direct flat form, not the 200K band partition.
|
|
1555
|
+
cache_create_cost = _whole_request_cache_create_cost(
|
|
1556
|
+
pricing,
|
|
1557
|
+
usage.get("cache_creation_input_tokens", 0),
|
|
1558
|
+
usage.get("cache_creation_1h_input_tokens"),
|
|
1559
|
+
)
|
|
1370
1560
|
cache_read_cost = _tiered(
|
|
1371
1561
|
usage.get("cache_read_input_tokens", 0),
|
|
1372
1562
|
"cache_read_input_token_cost",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""Pure-fn kernel for the pricing-freshness check (spec 2026-05-29).
|
|
2
2
|
|
|
3
|
-
No I/O, no import of `cctally`/`_lib_pricing` at module scope
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
No I/O, and no import of `cctally`/`_lib_pricing` at module scope. The
|
|
4
|
+
tables, observed rows and LiteLLM snapshot are passed in by the I/O glue in
|
|
5
|
+
bin/cctally, which re-exports this module like the other _lib_* kernels.
|
|
6
|
+
The one exception is `check_table_shapes`, which resolves the pure
|
|
7
|
+
`_lib_pricing` kernel per call through `_pricing()` (#929) so the
|
|
8
|
+
whole-request declaration rule has a single definition shared with the
|
|
9
|
+
cost engine's selector.
|
|
7
10
|
"""
|
|
8
11
|
from __future__ import annotations
|
|
9
12
|
|
|
@@ -186,6 +189,13 @@ def expired_allowlist_entries(allowlist, as_of_date) -> list:
|
|
|
186
189
|
]
|
|
187
190
|
|
|
188
191
|
|
|
192
|
+
def _pricing():
|
|
193
|
+
"""The pricing kernel, resolved per call (never bound at module scope —
|
|
194
|
+
see this module's docstring). Mirrors ``_lib_cost_provenance._pricing``."""
|
|
195
|
+
import _lib_pricing
|
|
196
|
+
return _lib_pricing
|
|
197
|
+
|
|
198
|
+
|
|
189
199
|
_CLAUDE_REQUIRED = ("input_cost_per_token", "output_cost_per_token",
|
|
190
200
|
"cache_creation_input_token_cost", "cache_read_input_token_cost")
|
|
191
201
|
_CODEX_REQUIRED = ("input_cost_per_token", "cache_read_input_token_cost",
|
|
@@ -211,8 +221,14 @@ def check_table_shapes(claude_tbl, codex_tbl, zero_sentinels) -> list:
|
|
|
211
221
|
if cost_fields and all(float(v) == 0.0 for v in cost_fields.values()) and not allow_zero:
|
|
212
222
|
problems.append(f"{model}: all cost fields zero but not a documented sentinel")
|
|
213
223
|
|
|
224
|
+
# #929: a Claude whole-request (prompt-length) tier is all-or-nothing.
|
|
225
|
+
# The definition of "well formed" lives beside the selector that refuses a
|
|
226
|
+
# malformed card, so this check and the cost engine can never disagree.
|
|
227
|
+
declaration_problems = _pricing().claude_whole_request_declaration_problems
|
|
214
228
|
for model, body in claude_tbl.items():
|
|
215
229
|
_check(model, body, _CLAUDE_REQUIRED, allow_zero=False)
|
|
230
|
+
for p in declaration_problems(body):
|
|
231
|
+
problems.append(f"{model}: {p}")
|
|
216
232
|
for model, body in codex_tbl.items():
|
|
217
233
|
_check(model, body, _CODEX_REQUIRED, allow_zero=model in zero_sentinels)
|
|
218
234
|
return problems
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The one parser of tests/quarantine.json, the reviewed flaky-test list (spec 4.3).
|
|
3
|
+
|
|
4
|
+
Stdlib only. An entry's `id` takes one of two forms:
|
|
5
|
+
|
|
6
|
+
* a pytest node id, `tests/<file>.py::<node>` (a prefix: pytest's --deselect
|
|
7
|
+
semantics cover every parametrization of a listed function);
|
|
8
|
+
* a harness case, `harness:<harness-name>::<case>`, where `<harness-name>` is
|
|
9
|
+
the `NAME` of `bin/cctally-NAME-test` and `<case>` is one scenario of that
|
|
10
|
+
harness's own case inventory (`bin/cctally-NAME-test --list-cases`).
|
|
11
|
+
|
|
12
|
+
`bin/cctally-test-all`, `bin/_lib_merge_gate.py` and the quick gate read the
|
|
13
|
+
list through this module; the scheduled CI quarantine leg calls its CLI. An
|
|
14
|
+
absent list quarantines nothing (the public mirror may carry none); a list
|
|
15
|
+
that exists but cannot be read raises QuarantineError, never "nothing listed".
|
|
16
|
+
|
|
17
|
+
CLI (exit 0, or 2 on an unreadable list or bad usage):
|
|
18
|
+
python3 bin/_lib_quarantine.py pytest-ids
|
|
19
|
+
python3 bin/_lib_quarantine.py harness-cases <harness>
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
import json
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
import re
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
LIST_PATH = Path('tests') / 'quarantine.json'
|
|
30
|
+
FIELDS = ('id', 'evidence', 'invariant', 'issue', 'replacement', 'reviewBy')
|
|
31
|
+
PYTEST_ID = re.compile(r'tests/[^:\s]+\.py::\S+')
|
|
32
|
+
HARNESS_ID = re.compile(r'harness:([a-z0-9][a-z0-9-]*)::([A-Za-z0-9][A-Za-z0-9_.-]*)')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class QuarantineError(ValueError):
|
|
36
|
+
"""tests/quarantine.json exists but is not a list this parser can apply."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class Entry:
|
|
41
|
+
id: str
|
|
42
|
+
kind: str # 'pytest' or 'harness'
|
|
43
|
+
harness: str | None # the harness name for a harness case
|
|
44
|
+
case: str | None # the case name for a harness case
|
|
45
|
+
fields: dict
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def parse_id(entry_id: str) -> tuple[str, str | None, str | None]:
|
|
49
|
+
"""(kind, harness, case) of one id; QuarantineError for any other form."""
|
|
50
|
+
if not isinstance(entry_id, str):
|
|
51
|
+
raise QuarantineError(f'an entry id must be a string, not {entry_id!r}')
|
|
52
|
+
if PYTEST_ID.fullmatch(entry_id):
|
|
53
|
+
return 'pytest', None, None
|
|
54
|
+
match = HARNESS_ID.fullmatch(entry_id)
|
|
55
|
+
if match:
|
|
56
|
+
return 'harness', match.group(1), match.group(2)
|
|
57
|
+
raise QuarantineError(
|
|
58
|
+
f'{entry_id!r} is neither a pytest node id (tests/<file>.py::<node>) '
|
|
59
|
+
'nor a harness case (harness:<harness>::<case>)')
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse(doc) -> list[Entry]:
|
|
63
|
+
if not isinstance(doc, list):
|
|
64
|
+
raise QuarantineError('expected a JSON list of entries')
|
|
65
|
+
entries = []
|
|
66
|
+
for item in doc:
|
|
67
|
+
if not isinstance(item, dict):
|
|
68
|
+
raise QuarantineError(f'expected each entry to be an object, not {item!r}')
|
|
69
|
+
kind, harness, case = parse_id(item.get('id'))
|
|
70
|
+
entries.append(Entry(item['id'], kind, harness, case, dict(item)))
|
|
71
|
+
ids = [e.id for e in entries]
|
|
72
|
+
if len(set(ids)) != len(ids):
|
|
73
|
+
raise QuarantineError('duplicate entry ids: ' + ', '.join(
|
|
74
|
+
sorted({i for i in ids if ids.count(i) > 1})))
|
|
75
|
+
return entries
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_path(path: Path) -> list[Entry]:
|
|
79
|
+
path = Path(path)
|
|
80
|
+
if not path.is_file():
|
|
81
|
+
return []
|
|
82
|
+
try:
|
|
83
|
+
doc = json.loads(path.read_text(encoding='utf-8'))
|
|
84
|
+
except (OSError, ValueError) as exc:
|
|
85
|
+
raise QuarantineError(f'{path}: {exc}') from None
|
|
86
|
+
return parse(doc)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def load(repo: Path) -> list[Entry]:
|
|
90
|
+
return load_path(Path(repo) / LIST_PATH)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def pytest_ids(entries) -> list[str]:
|
|
94
|
+
return [e.id for e in entries if e.kind == 'pytest']
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def harness_cases(entries, harness: str) -> list[str]:
|
|
98
|
+
return [e.case for e in entries if e.kind == 'harness' and e.harness == harness]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def harness_inventory(repo: Path, harness: str, *, timeout: float = 60) -> list[str]:
|
|
102
|
+
"""The case names `bin/cctally-<harness>-test --list-cases` prints.
|
|
103
|
+
|
|
104
|
+
QuarantineError when the harness does not exist or has no case inventory:
|
|
105
|
+
a harness case that cannot be validated against the harness is not
|
|
106
|
+
quarantinable."""
|
|
107
|
+
script = Path(repo) / 'bin' / f'cctally-{harness}-test'
|
|
108
|
+
if not script.is_file():
|
|
109
|
+
raise QuarantineError(f'harness:{harness}: bin/cctally-{harness}-test does not exist')
|
|
110
|
+
proc = subprocess.run([str(script), '--list-cases'], cwd=repo, capture_output=True,
|
|
111
|
+
text=True, timeout=timeout)
|
|
112
|
+
if proc.returncode != 0:
|
|
113
|
+
raise QuarantineError(f'harness:{harness}: --list-cases exited {proc.returncode}: '
|
|
114
|
+
+ (proc.stderr or proc.stdout).strip()[-400:])
|
|
115
|
+
return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def validate_harness_cases(repo: Path, entries) -> None:
|
|
119
|
+
"""Each harness case names an existing harness and a case in its inventory."""
|
|
120
|
+
inventories: dict[str, list[str]] = {}
|
|
121
|
+
for entry in entries:
|
|
122
|
+
if entry.kind != 'harness':
|
|
123
|
+
continue
|
|
124
|
+
if entry.harness not in inventories:
|
|
125
|
+
inventories[entry.harness] = harness_inventory(repo, entry.harness)
|
|
126
|
+
if entry.case not in inventories[entry.harness]:
|
|
127
|
+
raise QuarantineError(f'{entry.id}: no case {entry.case!r} in the '
|
|
128
|
+
f'{entry.harness} harness inventory')
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def main(argv=None) -> int:
|
|
132
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
133
|
+
repo = Path(__file__).resolve().parents[1]
|
|
134
|
+
if len(argv) >= 2 and argv[0] == '--repo':
|
|
135
|
+
repo, argv = Path(argv[1]), argv[2:]
|
|
136
|
+
try:
|
|
137
|
+
if argv == ['pytest-ids']:
|
|
138
|
+
out = pytest_ids(load(repo))
|
|
139
|
+
elif len(argv) == 2 and argv[0] == 'harness-cases':
|
|
140
|
+
out = harness_cases(load(repo), argv[1])
|
|
141
|
+
else:
|
|
142
|
+
print('usage: _lib_quarantine.py [--repo DIR] pytest-ids | harness-cases <harness>',
|
|
143
|
+
file=sys.stderr)
|
|
144
|
+
return 2
|
|
145
|
+
except QuarantineError as exc:
|
|
146
|
+
print(f'quarantine: {exc}', file=sys.stderr)
|
|
147
|
+
return 2
|
|
148
|
+
for item in out:
|
|
149
|
+
print(item)
|
|
150
|
+
return 0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == '__main__':
|
|
154
|
+
raise SystemExit(main())
|
|
@@ -62,6 +62,10 @@ class ApplyRejection(str, enum.Enum):
|
|
|
62
62
|
|
|
63
63
|
UNSUPPORTED_COMPOSITION = "unsupported-composition"
|
|
64
64
|
EMPTY_POPULATION = "no-local-history"
|
|
65
|
+
#: The requested interval, or an entry of the population, lies outside
|
|
66
|
+
#: the regime's own coefficient era (#978 spec 4.6). Units from another
|
|
67
|
+
#: era are never divided by this regime's units-per-point.
|
|
68
|
+
UNVALIDATED_COEFFICIENT_ERA = "unvalidated-coefficient-era"
|
|
65
69
|
|
|
66
70
|
|
|
67
71
|
@dataclasses.dataclass(frozen=True)
|
|
@@ -88,6 +92,9 @@ class ValidatedRegime:
|
|
|
88
92
|
class_shares: dict
|
|
89
93
|
family_radius: "float | None"
|
|
90
94
|
class_radius: "float | None"
|
|
95
|
+
#: The id of the coefficient era the regime was fitted in, always a
|
|
96
|
+
#: member of `qm.COEFFICIENT_ERAS` (#978).
|
|
97
|
+
coefficient_era: str
|
|
91
98
|
qualifications: tuple = ()
|
|
92
99
|
|
|
93
100
|
|
|
@@ -196,6 +203,13 @@ def validate_regime(raw, *, account_key, expected_fingerprint,
|
|
|
196
203
|
if isinstance(revision, bool) or not isinstance(revision, int) \
|
|
197
204
|
or revision != expected_revision:
|
|
198
205
|
return RegimeRejection.REVISION
|
|
206
|
+
# #978 spec 4.5: under the current constants a regime names the
|
|
207
|
+
# coefficient era it was fitted in. Missing or unknown is malformed, never
|
|
208
|
+
# a guess at an era; a regime from an older build has already been
|
|
209
|
+
# reported above as a fingerprint mismatch.
|
|
210
|
+
era = qm.era_by_id(raw.get("coefficientEra"))
|
|
211
|
+
if era is None:
|
|
212
|
+
return RegimeRejection.MALFORMED
|
|
199
213
|
|
|
200
214
|
point = raw.get("unitsPerPoint")
|
|
201
215
|
if not _finite(point) or point <= 0.0:
|
|
@@ -256,31 +270,42 @@ def validate_regime(raw, *, account_key, expected_fingerprint,
|
|
|
256
270
|
class_shares=class_shares,
|
|
257
271
|
family_radius=family_radius,
|
|
258
272
|
class_radius=class_radius,
|
|
273
|
+
coefficient_era=era.id,
|
|
259
274
|
qualifications=marks,
|
|
260
275
|
)
|
|
261
276
|
|
|
262
277
|
|
|
263
|
-
def population_units(entries) -> float:
|
|
278
|
+
def population_units(entries, *, era) -> float:
|
|
264
279
|
"""Summed weighted quota units over the general-meter entries.
|
|
265
280
|
|
|
266
281
|
The same filter `_cctally_quota_model.analyse_account` applies to the
|
|
267
282
|
current week: an entry whose family does not drain the general weekly
|
|
268
283
|
meter contributes nothing, and one whose cache-write split is unknown is
|
|
269
|
-
skipped rather than priced at zero.
|
|
284
|
+
skipped rather than priced at zero. Every entry is weighted with `era`,
|
|
285
|
+
the regime's own coefficient era (#978).
|
|
286
|
+
|
|
287
|
+
An entry `era` does not cover raises `CoefficientEraMismatch`, as the
|
|
288
|
+
kernel's population functions do (#978 spec 4.2, invariant 5): skipping
|
|
289
|
+
it would divide part of the population by the regime's budget and report
|
|
290
|
+
the result as the whole.
|
|
270
291
|
"""
|
|
271
292
|
total = 0.0
|
|
272
293
|
for entry in entries:
|
|
273
294
|
if qm.family_participation(qm.normalize_family(entry.model)) \
|
|
274
295
|
!= "general":
|
|
275
296
|
continue
|
|
276
|
-
|
|
277
|
-
if
|
|
297
|
+
weighed = qm.weigh_entry(entry, era)
|
|
298
|
+
if weighed.cause == qm.ERA_UNAVAILABLE:
|
|
299
|
+
raise qm.CoefficientEraMismatch(
|
|
300
|
+
f"entry.at {entry.at.isoformat()} lies outside coefficient "
|
|
301
|
+
f"era {era.id!r}")
|
|
302
|
+
if weighed.units is None:
|
|
278
303
|
continue
|
|
279
|
-
total += units
|
|
304
|
+
total += weighed.units
|
|
280
305
|
return total
|
|
281
306
|
|
|
282
307
|
|
|
283
|
-
def apply_regime(regime: ValidatedRegime, entries
|
|
308
|
+
def apply_regime(regime: ValidatedRegime, entries, *, interval
|
|
284
309
|
) -> "AppliedQuota | ApplyRejection":
|
|
285
310
|
"""Modelled consumption for `entries` under `regime`.
|
|
286
311
|
|
|
@@ -288,8 +313,28 @@ def apply_regime(regime: ValidatedRegime, entries
|
|
|
288
313
|
status, because that status describes S1's fit population and this one is
|
|
289
314
|
the consumer's. The arithmetic afterwards reproduces the kernel's, so a
|
|
290
315
|
consumer never re-derives the inversion by hand.
|
|
316
|
+
|
|
317
|
+
`interval` is the half-open `[start, end)` the caller models, which a
|
|
318
|
+
consumer extrapolating over a whole week must pass as that week (#978
|
|
319
|
+
spec 4.6). Two independent checks run before any aggregation: the
|
|
320
|
+
interval must lie inside the regime's coefficient era, and so must every
|
|
321
|
+
entry. The interval check is not implied by the entry check — a week
|
|
322
|
+
starting before the era whose retained entries all fall after its start
|
|
323
|
+
is in-era entry by entry, yet modelling the whole week is forbidden.
|
|
291
324
|
"""
|
|
292
|
-
|
|
325
|
+
era = qm.era_by_id(regime.coefficient_era)
|
|
326
|
+
if era is None:
|
|
327
|
+
return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
|
|
328
|
+
start, end = interval
|
|
329
|
+
qm.require_aware(start, "interval.start")
|
|
330
|
+
qm.require_aware(end, "interval.end")
|
|
331
|
+
era_start, era_end = qm.era_bounds(era)
|
|
332
|
+
if start < era_start or (era_end is not None and end > era_end):
|
|
333
|
+
return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
|
|
334
|
+
entries = list(entries)
|
|
335
|
+
if any(not qm.era_contains(era, entry.at) for entry in entries):
|
|
336
|
+
return ApplyRejection.UNVALIDATED_COEFFICIENT_ERA
|
|
337
|
+
aggregated = qm.aggregate_composition(entries, era=era)
|
|
293
338
|
if aggregated is None:
|
|
294
339
|
return ApplyRejection.EMPTY_POPULATION
|
|
295
340
|
family_shares, class_shares = aggregated
|
|
@@ -298,7 +343,7 @@ def apply_regime(regime: ValidatedRegime, entries
|
|
|
298
343
|
regime.class_shares, regime.class_radius):
|
|
299
344
|
return ApplyRejection.UNSUPPORTED_COMPOSITION
|
|
300
345
|
|
|
301
|
-
units = population_units(entries)
|
|
346
|
+
units = population_units(entries, era=era)
|
|
302
347
|
if not _finite(units) or units < 0.0:
|
|
303
348
|
return ApplyRejection.EMPTY_POPULATION
|
|
304
349
|
|
package/bin/_lib_quota_copy.py
CHANGED
|
@@ -70,7 +70,8 @@ _LONG_FORM: dict = {
|
|
|
70
70
|
"the models in use sit outside the mix the calibration was fitted "
|
|
71
71
|
"over",
|
|
72
72
|
"unvalidated-coefficient-era":
|
|
73
|
-
"this window
|
|
73
|
+
"this window is not inside a single coefficient era the token "
|
|
74
|
+
"weights are validated for",
|
|
74
75
|
"stale":
|
|
75
76
|
"the stored calibration was fitted under different constants, so it "
|
|
76
77
|
"no longer describes this build",
|