cctally 1.74.0 → 1.75.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.
@@ -35,6 +35,7 @@ from _lib_quota import (
35
35
  quota_rule_fingerprint,
36
36
  quota_threshold_decisions,
37
37
  percent_milestones,
38
+ physical_order_key,
38
39
  resolve_quota_rule,
39
40
  select_baseline,
40
41
  source_path_key,
@@ -902,8 +903,10 @@ def reconcile_codex_quota_projection(
902
903
 
903
904
  def _load_active_milestones(
904
905
  identity: QuotaWindowIdentity, resets_at: dt.datetime,
905
- ) -> list[sqlite3.Row]:
906
- stats = _cctally_core.open_db()
906
+ *, stats_conn: sqlite3.Connection | None = None,
907
+ ) -> list[sqlite3.Row | tuple[object, ...]]:
908
+ owns_conn = stats_conn is None
909
+ stats = _cctally_core.open_db() if stats_conn is None else stats_conn
907
910
  try:
908
911
  return list(stats.execute(
909
912
  """SELECT percent_threshold, captured_at_utc, source_path, line_offset
@@ -918,24 +921,62 @@ def _load_active_milestones(
918
921
  ),
919
922
  ))
920
923
  finally:
921
- stats.close()
924
+ if owns_conn:
925
+ stats.close()
922
926
 
923
927
 
924
- def _matching_block_observations(
928
+ def _first_block_physical_tuple(
925
929
  identity: QuotaWindowIdentity, resets_at: dt.datetime,
926
- ) -> tuple[QuotaObservation, ...]:
927
- return tuple(
928
- observation for observation in load_codex_quota_observations(
929
- source_root_keys={identity.source_root_key},
930
- )
931
- if observation.identity == identity and observation.resets_at == resets_at
932
- )
930
+ *, cache_conn: sqlite3.Connection | None = None,
931
+ ) -> tuple[dt.datetime, str, int] | None:
932
+ """Read the first physical tuple for one exact projected block.
933
+
934
+ The prior implementation reconstructed every retained quota observation
935
+ for the root in Python just to discover this boundary. Keep the same
936
+ physical ordering while letting SQLite filter the exact identity/reset.
937
+ ``unixepoch`` deliberately accepts retained ``Z`` and ``+00:00`` spellings.
938
+ """
939
+ owns_conn = cache_conn is None
940
+ if cache_conn is None:
941
+ try:
942
+ cache = _cache_connection()
943
+ except (FileNotFoundError, sqlite3.Error):
944
+ return None
945
+ else:
946
+ cache = cache_conn
947
+ try:
948
+ row = cache.execute(
949
+ """SELECT captured_at_utc, source_path, line_offset
950
+ FROM quota_window_snapshots
951
+ WHERE source='codex' AND source_root_key=?
952
+ AND logical_limit_key=? AND observed_slot=?
953
+ AND window_minutes=?
954
+ AND unixepoch(resets_at_utc)=unixepoch(?)
955
+ ORDER BY unixepoch(captured_at_utc), unixepoch(resets_at_utc),
956
+ source_path, line_offset
957
+ LIMIT 1""",
958
+ (
959
+ identity.source_root_key, identity.logical_limit_key,
960
+ identity.observed_slot, identity.window_minutes,
961
+ _utc_iso(resets_at),
962
+ ),
963
+ ).fetchone()
964
+ finally:
965
+ if owns_conn:
966
+ cache.close()
967
+ if row is None:
968
+ return None
969
+ try:
970
+ return (_parse_utc(str(row[0]), "captured_at_utc"), str(row[1]), int(row[2]))
971
+ except (TypeError, ValueError):
972
+ return None
933
973
 
934
974
 
935
975
  def codex_quota_breakdown(
936
976
  identity: QuotaWindowIdentity,
937
977
  resets_at: str | dt.datetime,
938
- *, speed: str = "auto",
978
+ *, speed: str = "auto", cache_conn: sqlite3.Connection | None = None,
979
+ stats_conn: sqlite3.Connection | None = None,
939
980
  ) -> tuple[CodexQuotaBreakdownRow, ...]:
940
981
  """Correlate durable milestone boundaries with live-priced cache accounting.
941
982
 
@@ -948,16 +989,34 @@ def codex_quota_breakdown(
948
989
  if reset.tzinfo is None or reset.utcoffset() is None:
949
990
  raise ValueError("resets_at must be timezone-aware")
950
991
  reset = reset.astimezone(UTC)
951
- points = sorted(_matching_block_observations(identity, reset), key=_physical_tuple)
952
- if not points:
953
- return ()
954
- milestones = _load_active_milestones(identity, reset)
992
+ milestones = _load_active_milestones(identity, reset, stats_conn=stats_conn)
955
993
  if not milestones:
956
994
  return ()
957
- try:
958
- cache = _cache_connection()
959
- except (FileNotFoundError, sqlite3.Error):
995
+ owns_cache = cache_conn is None
996
+ if cache_conn is None:
997
+ try:
998
+ cache = _cache_connection()
999
+ except (FileNotFoundError, sqlite3.Error):
1000
+ return ()
1001
+ else:
1002
+ cache = cache_conn
1003
+ start = _first_block_physical_tuple(identity, reset, cache_conn=cache)
1004
+ if start is None:
1005
+ if owns_cache:
1006
+ cache.close()
960
1007
  return ()
1008
+ # ``timestamp_utc`` is stored in canonical ``Z`` form, while retained
1009
+ # quota observations may have arrived as ``+00:00``. Keep this SQL bound
1010
+ # deliberately one second wider and let the physical-tuple comparison
1011
+ # below enforce the exact inclusive endpoint. This preserves same-second
1012
+ # path/offset ordering without relying on mixed-spelling text equality.
1013
+ query_start = (
1014
+ start[0] - dt.timedelta(seconds=1)
1015
+ ).astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
1016
+ query_end = (
1017
+ _parse_utc(str(milestones[-1][1]), "captured_at_utc")
1018
+ + dt.timedelta(seconds=1)
1019
+ ).astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
961
1020
  try:
962
1021
  entries = []
963
1022
  for row in cache.execute(
@@ -965,21 +1024,28 @@ def codex_quota_breakdown(
965
1024
  input_tokens, cached_input_tokens, output_tokens,
966
1025
  reasoning_output_tokens, total_tokens
967
1026
  FROM codex_session_entries
968
- WHERE source_root_key=?""",
969
- (identity.source_root_key,),
1027
+ WHERE source_root_key=?
1028
+ AND timestamp_utc>=? AND timestamp_utc<=?""",
1029
+ (
1030
+ identity.source_root_key,
1031
+ query_start,
1032
+ query_end,
1033
+ ),
970
1034
  ):
971
1035
  try:
972
- physical = (_parse_utc(str(row["timestamp_utc"]), "timestamp_utc"),
973
- str(row["source_path"]), int(row["line_offset"]))
1036
+ physical = (
1037
+ _parse_utc(str(row[0]), "timestamp_utc"),
1038
+ str(row[1]), int(row[2]),
1039
+ )
974
1040
  except (TypeError, ValueError):
975
1041
  continue
976
1042
  entries.append((physical, row))
977
1043
  finally:
978
- cache.close()
1044
+ if owns_cache:
1045
+ cache.close()
979
1046
  entries.sort(key=lambda pair: pair[0])
980
1047
  resolved_speed = sys.modules["cctally"]._resolve_codex_speed(speed)
981
1048
  calculate_cost = sys.modules["cctally"]._calculate_codex_entry_cost
982
- start = _physical_tuple(points[0])
983
1049
  prior_cumulative = 0.0
984
1050
  cumulative_input = 0
985
1051
  cumulative_cached = 0
@@ -989,20 +1055,19 @@ def codex_quota_breakdown(
989
1055
  result: list[CodexQuotaBreakdownRow] = []
990
1056
  for milestone in milestones:
991
1057
  end = (
992
- _parse_utc(str(milestone["captured_at_utc"]), "captured_at_utc"),
993
- str(milestone["source_path"]), int(milestone["line_offset"]),
1058
+ _parse_utc(str(milestone[1]), "captured_at_utc"),
1059
+ str(milestone[2]), int(milestone[3]),
994
1060
  )
995
1061
  selected = [row for physical, row in entries if start < physical <= end]
996
- input_tokens = sum(int(row["input_tokens"]) for row in selected)
997
- cached = sum(int(row["cached_input_tokens"]) for row in selected)
998
- output = sum(int(row["output_tokens"]) for row in selected)
999
- reasoning = sum(int(row["reasoning_output_tokens"]) for row in selected)
1000
- total = sum(int(row["total_tokens"]) for row in selected)
1062
+ input_tokens = sum(int(row[4]) for row in selected)
1063
+ cached = sum(int(row[5]) for row in selected)
1064
+ output = sum(int(row[6]) for row in selected)
1065
+ reasoning = sum(int(row[7]) for row in selected)
1066
+ total = sum(int(row[8]) for row in selected)
1001
1067
  marginal = sum(
1002
1068
  calculate_cost(
1003
- str(row["model"]), int(row["input_tokens"]),
1004
- int(row["cached_input_tokens"]), int(row["output_tokens"]),
1005
- int(row["reasoning_output_tokens"]), speed=resolved_speed,
1069
+ str(row[3]), int(row[4]), int(row[5]), int(row[6]),
1070
+ int(row[7]), speed=resolved_speed,
1006
1071
  )
1007
1072
  for row in selected
1008
1073
  )
@@ -1013,7 +1078,7 @@ def codex_quota_breakdown(
1013
1078
  cumulative_reasoning += reasoning
1014
1079
  cumulative_total += total
1015
1080
  result.append(CodexQuotaBreakdownRow(
1016
- percent=int(milestone["percent_threshold"]), captured_at=end[0],
1081
+ percent=int(milestone[0]), captured_at=end[0],
1017
1082
  input_tokens=cumulative_input, cached_input_tokens=cumulative_cached,
1018
1083
  output_tokens=cumulative_output, reasoning_output_tokens=cumulative_reasoning,
1019
1084
  total_tokens=cumulative_total, cost_usd=cumulative,
@@ -1024,6 +1089,76 @@ def codex_quota_breakdown(
1024
1089
  return tuple(result)
1025
1090
 
1026
1091
 
1092
+ def codex_five_hour_percent_at_crossing(
1093
+ identity: QuotaWindowIdentity,
1094
+ captured_at: dt.datetime,
1095
+ observations: Iterable[QuotaObservation] | None = None,
1096
+ *, cache_conn: sqlite3.Connection | None = None,
1097
+ ) -> float | None:
1098
+ """Return the latest matching native five-hour percent at one crossing."""
1099
+ if observations is not None:
1100
+ eligible = tuple(
1101
+ observation for observation in observations
1102
+ if observation.identity.source_root_key == identity.source_root_key
1103
+ and observation.identity.window_minutes == 300
1104
+ and observation.identity.observed_slot == identity.observed_slot
1105
+ and observation.identity.limit_id == identity.limit_id
1106
+ and observation.captured_at <= captured_at < observation.resets_at
1107
+ )
1108
+ if not eligible:
1109
+ return None
1110
+ return float(max(eligible, key=physical_order_key).used_percent)
1111
+
1112
+ owns_conn = cache_conn is None
1113
+ if cache_conn is None:
1114
+ try:
1115
+ cache = _cache_connection()
1116
+ except (FileNotFoundError, sqlite3.Error):
1117
+ return None
1118
+ else:
1119
+ cache = cache_conn
1120
+ # A valid 300-minute observation that still covers the crossing must have
1121
+ # been captured within the preceding native window. The extra hour keeps
1122
+ # seconds-level reset jitter and shortened/re-anchored blocks in range
1123
+ # while avoiding a root-wide history reconstruction.
1124
+ lower = (captured_at - dt.timedelta(hours=6)).astimezone(UTC).strftime(
1125
+ "%Y-%m-%dT%H:%M:%S"
1126
+ )
1127
+ upper = (captured_at + dt.timedelta(seconds=1)).astimezone(UTC).strftime(
1128
+ "%Y-%m-%dT%H:%M:%S"
1129
+ )
1130
+ try:
1131
+ rows = cache.execute(
1132
+ """SELECT captured_at_utc, resets_at_utc, source_path, line_offset,
1133
+ used_percent
1134
+ FROM quota_window_snapshots
1135
+ WHERE source='codex' AND source_root_key=?
1136
+ AND window_minutes=300 AND observed_slot=? AND limit_id IS ?
1137
+ AND captured_at_utc>=? AND captured_at_utc<?""",
1138
+ (
1139
+ identity.source_root_key, identity.observed_slot,
1140
+ identity.limit_id, lower, upper,
1141
+ ),
1142
+ ).fetchall()
1143
+ finally:
1144
+ if owns_conn:
1145
+ cache.close()
1146
+ eligible: list[tuple[tuple[dt.datetime, dt.datetime, str, int], float]] = []
1147
+ for row in rows:
1148
+ try:
1149
+ observed_at = _parse_utc(str(row[0]), "captured_at_utc")
1150
+ resets_at = _parse_utc(str(row[1]), "resets_at_utc")
1151
+ physical = (observed_at, resets_at, str(row[2]), int(row[3]))
1152
+ used_percent = float(row[4])
1153
+ except (TypeError, ValueError, OverflowError):
1154
+ continue
1155
+ if observed_at <= captured_at < resets_at:
1156
+ eligible.append((physical, used_percent))
1157
+ if not eligible:
1158
+ return None
1159
+ return max(eligible, key=lambda item: item[0])[1]
1160
+
1161
+
1027
1162
  # === Canonical nested `cctally codex quota` CLI ===========================
1028
1163
 
1029
1164
 
@@ -1167,23 +1302,40 @@ def _select_histories(
1167
1302
  return selected
1168
1303
 
1169
1304
 
1170
- def _sync_and_load(args, as_of: dt.datetime) -> tuple[QuotaHistory, ...]:
1305
+ def _sync_and_load(
1306
+ args, as_of: dt.datetime, *, current_fresh_only: bool = False,
1307
+ reconcile_projection: bool = True,
1308
+ ) -> tuple[QuotaHistory, ...]:
1171
1309
  c = _cctally()
1172
- if not getattr(args, "no_sync", False):
1310
+ sync_requested = getattr(args, "sync", None)
1311
+ should_sync = (
1312
+ bool(sync_requested)
1313
+ if sync_requested is not None
1314
+ else not getattr(args, "no_sync", False)
1315
+ )
1316
+ if should_sync:
1173
1317
  cache = c.open_cache_db()
1174
1318
  try:
1175
1319
  c.sync_codex_cache(cache)
1176
1320
  finally:
1177
1321
  cache.close()
1178
- # All five CLI leaves use the single durable projection reconciler. It
1179
- # gives breakdown its milestone index and heals a cache/stats interruption
1180
- # without ever reinterpreting or mutating physical cache evidence here.
1181
- reconcile_codex_quota_projection(now=as_of)
1182
- observations = load_codex_quota_observations()
1322
+ # The nested quota leaves heal the durable projection on every read. The
1323
+ # peer percent-breakdown command instead mirrors Claude's materialized-read
1324
+ # default; its explicit --sync path still reconciles before rendering.
1325
+ if reconcile_projection:
1326
+ reconcile_codex_quota_projection(now=as_of)
1327
+ observations = load_codex_quota_observations(
1328
+ captured_at_or_after=(
1329
+ as_of - dt.timedelta(hours=1) if current_fresh_only else None
1330
+ ),
1331
+ )
1183
1332
  return build_history(observations)
1184
1333
 
1185
1334
 
1186
- def _command_context(args, *, range_args: bool = False):
1335
+ def _command_context(
1336
+ args, *, range_args: bool = False, current_fresh_only: bool = False,
1337
+ reconcile_projection: bool = True,
1338
+ ):
1187
1339
  c = _cctally()
1188
1340
  config = c._load_claude_config_for_args(args)
1189
1341
  display_tz = c._resolve_display_tz_obj(config)
@@ -1194,7 +1346,10 @@ def _command_context(args, *, range_args: bool = False):
1194
1346
  until = _parse_range_bound(getattr(args, "until", None), display_tz=display_tz, option="--until")
1195
1347
  if since is not None and until is not None and until <= since:
1196
1348
  raise QuotaCLIError("--until must be after --since")
1197
- histories = _sync_and_load(args, as_of)
1349
+ histories = _sync_and_load(
1350
+ args, as_of, current_fresh_only=current_fresh_only,
1351
+ reconcile_projection=reconcile_projection,
1352
+ )
1198
1353
  selected = _select_histories(
1199
1354
  histories,
1200
1355
  root_key=getattr(args, "root_key", None),
@@ -1431,3 +1586,114 @@ def cmd_codex_quota_breakdown(args) -> int:
1431
1586
  if not rows:
1432
1587
  text_rows.append("No percent milestones.")
1433
1588
  return _emit(args, payload, "\n".join(text_rows))
1589
+
1590
+
1591
+ def cmd_codex_percent_breakdown(args) -> int:
1592
+ """Render one native seven-day Codex cycle in the Claude table design."""
1593
+ try:
1594
+ reset_value = getattr(args, "reset_at", None)
1595
+ as_of, _since, _until, selected = _command_context(
1596
+ args, current_fresh_only=not bool(reset_value),
1597
+ reconcile_projection=bool(getattr(args, "sync", False)),
1598
+ )
1599
+ histories = tuple(
1600
+ history for history in selected
1601
+ if history.identity.window_minutes == 10_080
1602
+ )
1603
+ if reset_value:
1604
+ reset_at = _parse_reset_at(reset_value)
1605
+ matching = tuple(
1606
+ (history, block)
1607
+ for history in histories
1608
+ for block in build_blocks(history.physical_observations)
1609
+ if block.resets_at == reset_at
1610
+ )
1611
+ else:
1612
+ matching = tuple(
1613
+ (history, block)
1614
+ for history in histories
1615
+ if (
1616
+ (baseline := select_baseline(history.observations, as_of))
1617
+ is not None
1618
+ and baseline.resets_at > as_of
1619
+ and quota_freshness(
1620
+ history.physical_observations, as_of,
1621
+ ).state == "fresh"
1622
+ )
1623
+ for block in build_blocks(history.physical_observations)
1624
+ if block.resets_at == baseline.resets_at
1625
+ )
1626
+ if len(matching) != 1:
1627
+ raise QuotaCLIError(
1628
+ "percent-breakdown matches no unique native 7-day quota cycle; "
1629
+ "use exact selectors or --reset-at for retained history; candidates:\n"
1630
+ + _candidate_text(histories)
1631
+ )
1632
+ except QuotaCLIError as exc:
1633
+ eprint(f"cctally codex percent-breakdown: {exc}")
1634
+ return 2
1635
+
1636
+ c = _cctally()
1637
+ speed = c._resolve_codex_speed(args.speed)
1638
+ history, block = matching[0]
1639
+ try:
1640
+ cache = _cache_connection()
1641
+ except (FileNotFoundError, sqlite3.Error):
1642
+ cache = None
1643
+ try:
1644
+ rows = codex_quota_breakdown(
1645
+ history.identity, block.resets_at, speed=speed, cache_conn=cache,
1646
+ )
1647
+ five_hour_observations = load_codex_quota_observations(
1648
+ source_root_keys={history.identity.source_root_key},
1649
+ cache_conn=cache,
1650
+ captured_at_or_after=(
1651
+ rows[0].captured_at - dt.timedelta(hours=6) if rows else as_of
1652
+ ),
1653
+ )
1654
+ milestone_list = [
1655
+ {
1656
+ "percentThreshold": row.percent,
1657
+ "cumulativeCostUSD": round(row.cost_usd, 9),
1658
+ "marginalCostUSD": round(row.marginal_cost_usd, 9),
1659
+ "capturedAt": _iso_z(row.captured_at),
1660
+ "fiveHourPercentAtCrossing": (
1661
+ round(five_hour_percent, 1)
1662
+ if (five_hour_percent := codex_five_hour_percent_at_crossing(
1663
+ history.identity, row.captured_at, five_hour_observations,
1664
+ )) is not None
1665
+ else None
1666
+ ),
1667
+ }
1668
+ for row in rows
1669
+ ]
1670
+ finally:
1671
+ if cache is not None:
1672
+ cache.close()
1673
+ week_start = block.nominal_start_at.astimezone(UTC)
1674
+ week_end = block.resets_at.astimezone(UTC)
1675
+ output = {
1676
+ "source": "codex",
1677
+ "identity": _identity_wire(history.identity),
1678
+ "weekStartDate": week_start.date().isoformat(),
1679
+ "weekEndDate": (week_end - dt.timedelta(microseconds=1)).date().isoformat(),
1680
+ "weekStartAt": _iso_z(week_start),
1681
+ "weekEndAt": _iso_z(week_end),
1682
+ "milestones": milestone_list,
1683
+ "generatedAt": _iso_z(as_of),
1684
+ }
1685
+ if args.json:
1686
+ print(json.dumps(stamp_schema_version(output), indent=2))
1687
+ return 0
1688
+
1689
+ config = c._load_claude_config_for_args(args)
1690
+ tz = c.resolve_display_tz(args, config)
1691
+ print(c._render_percent_breakdown_terminal(
1692
+ week_start_date=output["weekStartDate"],
1693
+ week_end_date=output["weekEndDate"],
1694
+ display_start_iso=output["weekStartAt"],
1695
+ display_end_iso=output["weekEndAt"],
1696
+ milestone_list=milestone_list,
1697
+ tz=tz,
1698
+ ))
1699
+ return 0
@@ -3593,6 +3593,7 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
3593
3593
  # ``_resolve_active_five_hour_reset_event_id`` to find
3594
3594
  # the active segment for the latest snapshot's window.
3595
3595
  need_5h_heal = False
3596
+ incoming_block_saved: dict[str, Any] | None = None
3596
3597
  window_key = latest_row["five_hour_window_key"]
3597
3598
  if window_key is not None:
3598
3599
  block_row = heal_conn.execute(
@@ -3647,10 +3648,55 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
3647
3648
  existing_5h_m, latest_5h_floor
3648
3649
  ):
3649
3650
  need_5h_heal = True
3651
+ # Window-rollover heal: this dedup tick observed a NEW 5h
3652
+ # window (its canonical ``five_hour_window_key`` differs
3653
+ # from the latest STORED snapshot's) whose
3654
+ # ``five_hour_blocks`` anchor does not exist yet. The dedup
3655
+ # above swallowed the snapshot insert because the weekly/5h
3656
+ # percents were flat, so ``latest_row`` still points at the
3657
+ # PREVIOUS window and the ``need_5h_heal`` probe only ever
3658
+ # checked that old (still-fresh) window — leaving the
3659
+ # current window unanchored. Materialize the missing block
3660
+ # now so ``blocks`` and the dashboard anchor the ACTIVE
3661
+ # block to its API-derived window instead of falling back to
3662
+ # the heuristic "~" until the percent next moves (the
3663
+ # statusline is unaffected — it renders the live
3664
+ # rate_limits, not the DB). Block-only: NO snapshot is
3665
+ # inserted (the tick stays deduped); the "latest snapshot"
3666
+ # weekly/5h surfaces and monotonicity clamps are untouched.
3667
+ # ``maybe_update_five_hour_block`` is an upsert keyed on
3668
+ # ``five_hour_window_key``, so later flat ticks in the same
3669
+ # window re-run it as an idempotent no-op.
3670
+ if (
3671
+ five_hour_window_key is not None
3672
+ and five_hour_percent is not None
3673
+ and five_hour_resets_at_str is not None
3674
+ and (
3675
+ window_key is None
3676
+ or int(window_key) != int(five_hour_window_key)
3677
+ )
3678
+ ):
3679
+ incoming_block_row = heal_conn.execute(
3680
+ "SELECT 1 FROM five_hour_blocks "
3681
+ "WHERE five_hour_window_key = ? LIMIT 1",
3682
+ (int(five_hour_window_key),),
3683
+ ).fetchone()
3684
+ if incoming_block_row is None:
3685
+ incoming_block_saved = {
3686
+ # ``id`` is extracted-but-unused by
3687
+ # maybe_update_five_hour_block; reuse latest_row's
3688
+ # for output-dict shape parity with a real insert.
3689
+ "id": int(latest_row["id"]),
3690
+ "capturedAt": now_utc_iso(),
3691
+ "weeklyPercent": weekly_percent,
3692
+ "fiveHourPercent": five_hour_percent,
3693
+ "fiveHourResetsAt": five_hour_resets_at_str,
3694
+ "fiveHourWindowKey": int(five_hour_window_key),
3695
+ }
3650
3696
  finally:
3651
3697
  heal_conn.close()
3652
3698
 
3653
- if need_milestone_heal or need_5h_heal:
3699
+ if need_milestone_heal or need_5h_heal or incoming_block_saved:
3654
3700
  if need_milestone_heal:
3655
3701
  try:
3656
3702
  maybe_record_milestone(latest_saved)
@@ -3661,6 +3707,11 @@ def cmd_record_usage(args: argparse.Namespace) -> int:
3661
3707
  maybe_update_five_hour_block(latest_saved)
3662
3708
  except Exception as exc:
3663
3709
  eprint(f"[5h-block] self-heal error: {exc}")
3710
+ if incoming_block_saved is not None:
3711
+ try:
3712
+ maybe_update_five_hour_block(incoming_block_saved)
3713
+ except Exception as exc:
3714
+ eprint(f"[5h-block] window-rollover heal error: {exc}")
3664
3715
 
3665
3716
  # Dollar-decoupled axes (budget / project-budget / projected) heal on
3666
3717
  # EVERY dedup tick — USD spend can cross a $ threshold while the
@@ -603,6 +603,28 @@ def _refresh_usage_inproc(timeout_seconds: float = 5.0) -> _RefreshUsageResult:
603
603
  if not token:
604
604
  return _RefreshUsageResult(status="no_oauth_token", reason="no token")
605
605
 
606
+ # Force-refresh deliberately bypasses the pre-fetch backoff gate, but its
607
+ # resulting 429/success transition must be ordered with the automatic
608
+ # refresh writer. Hold the canonical selected-state lock from the fetch
609
+ # through authoritative publication and the matching backoff update.
610
+ try:
611
+ with c._selected_state_lock():
612
+ return _refresh_usage_inproc_locked(
613
+ c,
614
+ token=token,
615
+ timeout_seconds=timeout_seconds,
616
+ )
617
+ except OSError as exc:
618
+ return _RefreshUsageResult(status="record_failed", reason=str(exc))
619
+
620
+
621
+ def _refresh_usage_inproc_locked(
622
+ c,
623
+ *,
624
+ token: str,
625
+ timeout_seconds: float,
626
+ ) -> _RefreshUsageResult:
627
+ """Force-refresh with the canonical selected-state lock already held."""
606
628
  try:
607
629
  api = c._fetch_oauth_usage(token=token, timeout_seconds=timeout_seconds)
608
630
  except RefreshUsageRateLimitError as exc:
@@ -683,6 +705,7 @@ def _refresh_usage_inproc(timeout_seconds: float = 5.0) -> _RefreshUsageResult:
683
705
  "sevenDay",
684
706
  *({"fiveHour"} if five_pct is not None else set()),
685
707
  },
708
+ lock_held=True,
686
709
  )
687
710
  if authoritative.status != "ok":
688
711
  return _RefreshUsageResult(
@@ -831,6 +854,9 @@ def cmd_refresh_usage(args: argparse.Namespace) -> int:
831
854
 
832
855
  if result.status == "record_failed":
833
856
  reason = result.reason or ""
857
+ if c._is_sqlite_corruption_error(reason):
858
+ eprint(f"cctally: {c._stats_corruption_guidance()}")
859
+ return 3
834
860
  if reason.startswith("exit "):
835
861
  try:
836
862
  rc = int(reason.split()[1])
@@ -940,9 +940,9 @@ def _authoritative_record_usage(
940
940
  ) -> _AuthoritativeRecordResult:
941
941
  """Record OAuth authority under write-ahead tombstones and reconcile it.
942
942
 
943
- ``lock_held`` is reserved for automatic polling, which holds the selected
944
- lock across its post-lock freshness/backoff recheck and network request.
945
- All other callers acquire the same blocking lock here.
943
+ ``lock_held`` is for OAuth refresh callers that already hold the selected
944
+ lock across their fetch, authoritative publication, and matching backoff
945
+ transition. All other callers acquire the same blocking lock here.
946
946
  """
947
947
  if not lock_held:
948
948
  try:
@@ -2546,6 +2546,9 @@ def _tui_build_source_bundle(
2546
2546
  week_start_name=semantics.week_start_name,
2547
2547
  speed=semantics.speed,
2548
2548
  codex_budget=semantics.codex_budget,
2549
+ codex_quota_actual_thresholds=semantics.codex_quota_actual_thresholds,
2550
+ codex_quota_projected_thresholds=semantics.codex_quota_projected_thresholds,
2551
+ cache_report_anomaly_threshold_pp=semantics.cache_report_anomaly_threshold_pp,
2549
2552
  ),
2550
2553
  data_version=codex_version,
2551
2554
  )
@@ -378,6 +378,13 @@ class CodexBucketUsage:
378
378
  cost_usd: float
379
379
  models: list[str] # Distinct full model names (first-seen order)
380
380
  model_breakdowns: list[dict[str, Any]] # Sorted by cost desc
381
+ # Native quota-cycle context. Calendar daily/monthly callers leave these
382
+ # null; the dashboard's subscription-week adapter fills them from the
383
+ # retained OpenAI rate-limit observations.
384
+ period_start_at: dt.datetime | None = None
385
+ period_end_at: dt.datetime | None = None
386
+ used_pct: float | None = None
387
+ dollar_per_pct: float | None = None
381
388
 
382
389
 
383
390
  @dataclass
@@ -359,9 +359,17 @@ def _aggregate_cache_by_day(
359
359
  cost = cost_calculator(entry.model, entry.usage, "auto", entry.cost_usd)
360
360
  create_tok = entry.usage.get("cache_creation_input_tokens", 0)
361
361
  read_tok = entry.usage.get("cache_read_input_tokens", 0)
362
- saved, wasted, net = _compute_entry_cache_dollars(
363
- entry.model, create_tok, read_tok, pricing=pricing,
364
- )
362
+ explicit_saved = getattr(entry, "cache_saved_usd", None)
363
+ explicit_wasted = getattr(entry, "cache_wasted_usd", None)
364
+ explicit_net = getattr(entry, "cache_net_usd", None)
365
+ if all(value is not None for value in (explicit_saved, explicit_wasted, explicit_net)):
366
+ saved, wasted, net = (
367
+ float(explicit_saved), float(explicit_wasted), float(explicit_net),
368
+ )
369
+ else:
370
+ saved, wasted, net = _compute_entry_cache_dollars(
371
+ entry.model, create_tok, read_tok, pricing=pricing,
372
+ )
365
373
  models = day_model_buckets.setdefault(day_key, {})
366
374
  b = models.setdefault(entry.model, _Bucket())
367
375
  b.input_tokens += entry.usage.get("input_tokens", 0)
@@ -792,12 +800,20 @@ def _aggregate_cache_breakdown(
792
800
  b.input_tokens += getattr(e, "input_tokens", 0)
793
801
  b.cache_creation_tokens += getattr(e, "cache_creation_tokens", 0)
794
802
  b.cache_read_tokens += getattr(e, "cache_read_tokens", 0)
795
- saved, wasted, net = _compute_entry_cache_dollars(
796
- getattr(e, "model", ""),
797
- getattr(e, "cache_creation_tokens", 0),
798
- getattr(e, "cache_read_tokens", 0),
799
- pricing=pricing,
800
- )
803
+ explicit_saved = getattr(e, "cache_saved_usd", None)
804
+ explicit_wasted = getattr(e, "cache_wasted_usd", None)
805
+ explicit_net = getattr(e, "cache_net_usd", None)
806
+ if all(value is not None for value in (explicit_saved, explicit_wasted, explicit_net)):
807
+ saved, wasted, net = (
808
+ float(explicit_saved), float(explicit_wasted), float(explicit_net),
809
+ )
810
+ else:
811
+ saved, wasted, net = _compute_entry_cache_dollars(
812
+ getattr(e, "model", ""),
813
+ getattr(e, "cache_creation_tokens", 0),
814
+ getattr(e, "cache_read_tokens", 0),
815
+ pricing=pricing,
816
+ )
801
817
  b.saved_usd += saved
802
818
  b.wasted_usd += wasted
803
819
  b.net_usd += net