cctally 1.103.0 → 1.105.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +6 -6
  3. package/bin/_cctally_alerts.py +104 -10
  4. package/bin/_cctally_cache.py +193 -71
  5. package/bin/_cctally_config.py +62 -2
  6. package/bin/_cctally_core.py +62 -1
  7. package/bin/_cctally_dashboard.py +694 -70
  8. package/bin/_cctally_dashboard_conversation.py +400 -6
  9. package/bin/_cctally_dashboard_envelope.py +336 -14
  10. package/bin/_cctally_dashboard_perf.py +93 -0
  11. package/bin/_cctally_dashboard_share.py +56 -25
  12. package/bin/_cctally_dashboard_sources.py +988 -94
  13. package/bin/_cctally_db.py +30 -3
  14. package/bin/_cctally_diagnosis_sources.py +699 -186
  15. package/bin/_cctally_doctor.py +77 -0
  16. package/bin/_cctally_forecast.py +932 -51
  17. package/bin/_cctally_journal.py +430 -24
  18. package/bin/_cctally_parser.py +66 -0
  19. package/bin/_cctally_project.py +535 -10
  20. package/bin/_cctally_quota.py +28 -0
  21. package/bin/_cctally_quota_calibration.py +146 -0
  22. package/bin/_cctally_quota_model.py +2142 -0
  23. package/bin/_cctally_record.py +204 -30
  24. package/bin/_cctally_share.py +16 -8
  25. package/bin/_cctally_statusline.py +34 -0
  26. package/bin/_cctally_tui.py +531 -115
  27. package/bin/_lib_codex_conversation_query.py +258 -12
  28. package/bin/_lib_codex_hooks.py +26 -0
  29. package/bin/_lib_conversation_query.py +145 -15
  30. package/bin/_lib_dashboard_json.py +105 -0
  31. package/bin/_lib_dashboard_settings_contract.py +2 -0
  32. package/bin/_lib_diagnosis.py +21 -2
  33. package/bin/_lib_doctor.py +215 -1
  34. package/bin/_lib_forecast.py +337 -43
  35. package/bin/_lib_ingest_frontier.py +889 -0
  36. package/bin/_lib_meter_rate_change.py +360 -0
  37. package/bin/_lib_perf.py +22 -0
  38. package/bin/_lib_pricing.py +30 -3
  39. package/bin/_lib_quota_calibration.py +311 -0
  40. package/bin/_lib_quota_copy.py +157 -0
  41. package/bin/_lib_quota_model.py +2520 -0
  42. package/bin/_lib_rate_change_delivery.py +119 -0
  43. package/bin/_lib_record.py +50 -0
  44. package/bin/_lib_rederive.py +10 -0
  45. package/bin/_lib_render.py +6 -0
  46. package/bin/_lib_retained_size.py +187 -0
  47. package/bin/_lib_share_templates.py +37 -5
  48. package/bin/_lib_snapshot_cache.py +504 -17
  49. package/bin/_lib_statusline.py +200 -2
  50. package/bin/_lib_tick_stats.py +28 -4
  51. package/bin/_lib_view_models.py +30 -12
  52. package/bin/cctally +32 -0
  53. package/dashboard/static/assets/ConversationsView-DA4j7Yso.js +72 -0
  54. package/dashboard/static/assets/DoctorModal-D4PbVnbE.js +1 -0
  55. package/dashboard/static/assets/ModalRoot-D4oSPxPp.js +1 -0
  56. package/dashboard/static/assets/ProjectsDrillPanel-DkPlB9JH.js +1 -0
  57. package/dashboard/static/assets/SourceDetailModal-BE7FONsS.js +1 -0
  58. package/dashboard/static/assets/UpdateModal-BJuI8glf.js +7 -0
  59. package/dashboard/static/assets/index-klO46NcU.css +1 -0
  60. package/dashboard/static/assets/index-w_bINkJ8.js +13 -0
  61. package/dashboard/static/assets/outlineNavigation-zDVm6Hdd.js +9 -0
  62. package/dashboard/static/assets/useKeymap-CJ-Pi17D.js +1 -0
  63. package/dashboard/static/dashboard.html +3 -2
  64. package/package.json +10 -1
  65. package/dashboard/static/assets/index-Di2hljvB.css +0 -1
  66. package/dashboard/static/assets/index-XYCIWjVG.js +0 -97
@@ -4,9 +4,13 @@ from __future__ import annotations
4
4
  import datetime as dt
5
5
  import hashlib
6
6
  import json
7
+ import os
7
8
  import pathlib
8
9
  import sqlite3
9
10
  import sys
11
+ import threading
12
+ import time
13
+ from collections import OrderedDict
10
14
  from collections.abc import Mapping
11
15
  from dataclasses import dataclass, replace
12
16
  from types import MappingProxyType, SimpleNamespace
@@ -32,7 +36,14 @@ from _cctally_source_analytics import (
32
36
  )
33
37
  import _lib_log
34
38
  import _lib_accounts
39
+ import _lib_perf
35
40
  import _lib_snapshot_cache
41
+ from _lib_retained_size import (
42
+ RETAINED_SIZE_WORK_LOCK,
43
+ RETAINED_SIZE_WORKER_DUTY,
44
+ RetainedSizeCancelled,
45
+ retained_size_bytes,
46
+ )
36
47
  from _lib_dashboard_sources import (
37
48
  CapabilityRecord,
38
49
  ProjectionCoherence,
@@ -86,7 +97,86 @@ from _lib_view_models import (
86
97
  UTC = dt.timezone.utc
87
98
 
88
99
 
89
- _CODEX_QUOTA_OBSERVATION_CACHE: dict[object, tuple[object, ...]] = {}
100
+ _CODEX_SOURCE_ACCELERATOR_DIRTY = True
101
+ _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK = threading.Lock()
102
+ _CODEX_SOURCE_ACCELERATOR_GENERATION = 0
103
+ _CACHE_MISSING = object()
104
+
105
+
106
+ def _mark_codex_source_accelerators_dirty() -> None:
107
+ """Record the first mutation since the last aggregate admission pass."""
108
+ global _CODEX_SOURCE_ACCELERATOR_DIRTY
109
+ global _CODEX_SOURCE_ACCELERATOR_GENERATION
110
+ if _CODEX_SOURCE_ACCELERATOR_DIRTY:
111
+ return
112
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
113
+ if not _CODEX_SOURCE_ACCELERATOR_DIRTY:
114
+ _CODEX_SOURCE_ACCELERATOR_DIRTY = True
115
+ _CODEX_SOURCE_ACCELERATOR_GENERATION += 1
116
+
117
+
118
+ class _ObservedCacheMixin:
119
+ """Make every retained-cache mutation visible to aggregate admission."""
120
+
121
+ def __setitem__(self, key, value):
122
+ super().__setitem__(key, value)
123
+ _mark_codex_source_accelerators_dirty()
124
+
125
+ def __delitem__(self, key):
126
+ super().__delitem__(key)
127
+ _mark_codex_source_accelerators_dirty()
128
+
129
+ def clear(self):
130
+ if self:
131
+ super().clear()
132
+ _mark_codex_source_accelerators_dirty()
133
+
134
+ def pop(self, key, default=_CACHE_MISSING):
135
+ existed = key in self
136
+ if default is _CACHE_MISSING:
137
+ value = super().pop(key)
138
+ else:
139
+ value = super().pop(key, default)
140
+ if existed:
141
+ _mark_codex_source_accelerators_dirty()
142
+ return value
143
+
144
+ def popitem(self, *args, **kwargs):
145
+ value = super().popitem(*args, **kwargs)
146
+ _mark_codex_source_accelerators_dirty()
147
+ return value
148
+
149
+ def setdefault(self, key, default=None):
150
+ existed = key in self
151
+ value = super().setdefault(key, default)
152
+ if not existed:
153
+ _mark_codex_source_accelerators_dirty()
154
+ return value
155
+
156
+ def update(self, *args, **kwargs):
157
+ before = len(self)
158
+ super().update(*args, **kwargs)
159
+ if args or kwargs or len(self) != before:
160
+ _mark_codex_source_accelerators_dirty()
161
+
162
+
163
+ class _ObservedDict(_ObservedCacheMixin, dict):
164
+ pass
165
+
166
+
167
+ class _ObservedOrderedDict(_ObservedCacheMixin, OrderedDict):
168
+ pass
169
+
170
+
171
+ _CODEX_QUOTA_OBSERVATION_MAX_ENTRIES = 32
172
+ _CODEX_QUOTA_OBSERVATION_MAX_BYTES = 64 * 1024 * 1024
173
+ _CODEX_QUOTA_OBSERVATION_CACHE: "OrderedDict[object, tuple[object, ...]]" = (
174
+ _ObservedOrderedDict())
175
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES: dict[object, int] = {}
176
+ _CODEX_QUOTA_OBSERVATION_CACHE_BYTES = 0
177
+ _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = 0
178
+ _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = 0
179
+ _CODEX_QUOTA_OBSERVATION_CACHE_LOCK = threading.Lock()
90
180
 
91
181
 
92
182
  def reset_codex_quota_observation_cache() -> None:
@@ -98,7 +188,51 @@ def reset_codex_quota_observation_cache() -> None:
98
188
  cold-reference comparisons use, and `_codex_source_caches` is what the
99
189
  build's own checkpoint/restore goes through.
100
190
  """
101
- _CODEX_QUOTA_OBSERVATION_CACHE.clear()
191
+ global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
192
+ global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
193
+ global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
194
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
195
+ _CODEX_QUOTA_OBSERVATION_CACHE.clear()
196
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES.clear()
197
+ _CODEX_QUOTA_OBSERVATION_CACHE_BYTES = 0
198
+ _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = 0
199
+ _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = 0
200
+
201
+
202
+ def codex_quota_observation_cache_stats() -> Mapping[str, int]:
203
+ """Retained-byte admission and eviction evidence for the #618 memo."""
204
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
205
+ return MappingProxyType({
206
+ "estimatedBytes": int(_CODEX_QUOTA_OBSERVATION_CACHE_BYTES),
207
+ "maxBytes": int(_CODEX_QUOTA_OBSERVATION_MAX_BYTES),
208
+ "entryCount": len(_CODEX_QUOTA_OBSERVATION_CACHE),
209
+ "maxEntries": int(_CODEX_QUOTA_OBSERVATION_MAX_ENTRIES),
210
+ "evictionCount": int(_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS),
211
+ "fallbackCount": int(_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS),
212
+ })
213
+
214
+
215
+ def _quota_observation_cache_checkpoint() -> tuple[dict, int, int, int]:
216
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
217
+ return (
218
+ dict(_CODEX_QUOTA_OBSERVATION_CACHE_SIZES),
219
+ int(_CODEX_QUOTA_OBSERVATION_CACHE_BYTES),
220
+ int(_CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS),
221
+ int(_CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS),
222
+ )
223
+
224
+
225
+ def _restore_quota_observation_cache_checkpoint(checkpoint) -> None:
226
+ global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
227
+ global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
228
+ global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
229
+ sizes, retained_bytes, evictions, fallbacks = checkpoint
230
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
231
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES.clear()
232
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES.update(sizes)
233
+ _CODEX_QUOTA_OBSERVATION_CACHE_BYTES = int(retained_bytes)
234
+ _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS = int(evictions)
235
+ _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS = int(fallbacks)
102
236
 
103
237
 
104
238
  def _codex_quota_reuse_identity(
@@ -280,34 +414,40 @@ def _cached_codex_quota_observations(**kwargs) -> tuple[object, ...]:
280
414
  None if physical_groups is None else tuple(sorted(physical_groups)),
281
415
  bool(kwargs.get("latest_per_identity", False)),
282
416
  )
283
- cached = _CODEX_QUOTA_OBSERVATION_CACHE.get(key)
284
- if cached is not None:
285
- return cached
417
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
418
+ cached = _CODEX_QUOTA_OBSERVATION_CACHE.get(key)
419
+ if cached is not None:
420
+ _CODEX_QUOTA_OBSERVATION_CACHE.move_to_end(key)
421
+ return cached
286
422
  loaded = tuple(load_codex_quota_observations(**kwargs))
287
- # The written memory bound (#583 S5 spec §2.1 "Memory"). Now that this memo
288
- # survives a build, "32 entries" is a bound on ENTRIES and says nothing on
289
- # its own about rows, so the worst-case retained row count is stated here:
290
- #
291
- # * at most 32 results are retained, and the whole memo is discarded on
292
- # reaching that count rather than evicted one entry at a time;
293
- # * a bounded dashboard read contributes at most
294
- # `DASHBOARD_QUOTA_OBSERVATION_LIMIT` (1,000) `QuotaObservation`s;
295
- # * the five-hour correlation read in `_quota_read_model` carries NO
296
- # `max_rows`. It is bounded only by one root's retained observations
297
- # captured at or after one weekly block's nominal start, which is the
298
- # term that actually sizes this cache, and it grows with the block's
299
- # age because an older block's nominal start reaches further back.
300
- # Measured on a store holding 276,391 Codex quota rows, over the seven
301
- # weekly blocks live at that instant: 15,129 observations for the
302
- # newest and 36,943 for the oldest. The worst case is therefore 32
303
- # times a population in the tens of thousands, not 32 times 1,000.
304
- # * the doctor's unbounded `latest_per_identity` read is NOT retained
305
- # here. `bin/_cctally_doctor.py` calls `load_codex_quota_observations`
306
- # directly rather than through this memo, so its all-history population
307
- # never enters this dict.
308
- if len(_CODEX_QUOTA_OBSERVATION_CACHE) >= 32:
309
- _CODEX_QUOTA_OBSERVATION_CACHE.clear()
310
- _CODEX_QUOTA_OBSERVATION_CACHE[key] = loaded
423
+ entry_bytes = retained_size_bytes(
424
+ (key, loaded), stop_after=_CODEX_QUOTA_OBSERVATION_MAX_BYTES)
425
+ global _CODEX_QUOTA_OBSERVATION_CACHE_BYTES
426
+ global _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS
427
+ global _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS
428
+ with _CODEX_QUOTA_OBSERVATION_CACHE_LOCK:
429
+ # Another request may have filled this identity while the loader ran.
430
+ cached = _CODEX_QUOTA_OBSERVATION_CACHE.get(key)
431
+ if cached is not None:
432
+ _CODEX_QUOTA_OBSERVATION_CACHE.move_to_end(key)
433
+ return cached
434
+ if entry_bytes > _CODEX_QUOTA_OBSERVATION_MAX_BYTES:
435
+ _CODEX_QUOTA_OBSERVATION_CACHE_FALLBACKS += 1
436
+ return loaded
437
+ while _CODEX_QUOTA_OBSERVATION_CACHE and (
438
+ len(_CODEX_QUOTA_OBSERVATION_CACHE)
439
+ >= _CODEX_QUOTA_OBSERVATION_MAX_ENTRIES
440
+ or _CODEX_QUOTA_OBSERVATION_CACHE_BYTES + entry_bytes
441
+ > _CODEX_QUOTA_OBSERVATION_MAX_BYTES
442
+ ):
443
+ old_key, _old_value = _CODEX_QUOTA_OBSERVATION_CACHE.popitem(
444
+ last=False)
445
+ _CODEX_QUOTA_OBSERVATION_CACHE_BYTES -= (
446
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES.pop(old_key))
447
+ _CODEX_QUOTA_OBSERVATION_CACHE_EVICTIONS += 1
448
+ _CODEX_QUOTA_OBSERVATION_CACHE[key] = loaded
449
+ _CODEX_QUOTA_OBSERVATION_CACHE_SIZES[key] = entry_bytes
450
+ _CODEX_QUOTA_OBSERVATION_CACHE_BYTES += entry_bytes
311
451
  return loaded
312
452
  SOURCE_HISTORY_LIMIT = 250
313
453
  DASHBOARD_QUOTA_OBSERVATION_LIMIT = 1000
@@ -1120,6 +1260,30 @@ class DashboardReadContext:
1120
1260
  raise ValueError("codex_budget must be a mapping or None")
1121
1261
 
1122
1262
 
1263
+ def _cache_database_identity(conn: sqlite3.Connection) -> tuple[object, ...]:
1264
+ """Identity of the cache.db file currently backing ``conn``.
1265
+
1266
+ A pathname is not an identity: rebuild publishes a replacement at the same
1267
+ pathname. The device/inode pair makes every process memo fail closed on
1268
+ that replacement. In-memory and unreadable paths use the connection
1269
+ object, which intentionally prevents reuse across a newly-opened handle.
1270
+ """
1271
+ try:
1272
+ db_path = next(
1273
+ str(row[2]) for row in conn.execute("PRAGMA database_list")
1274
+ if str(row[1]) == "main"
1275
+ )
1276
+ except (sqlite3.Error, StopIteration):
1277
+ return ("connection", id(conn))
1278
+ if not db_path:
1279
+ return ("connection", id(conn))
1280
+ try:
1281
+ stat = os.stat(db_path)
1282
+ except OSError:
1283
+ return ("path-unreadable", db_path, id(conn))
1284
+ return ("file", db_path, int(stat.st_dev), int(stat.st_ino))
1285
+
1286
+
1123
1287
  _RESOURCE_ROWS = {
1124
1288
  "session": ("sessions", "rows"),
1125
1289
  "project": ("projects", "rows"),
@@ -1386,9 +1550,9 @@ def _period_wire(view: Any) -> dict[str, object]:
1386
1550
  }
1387
1551
 
1388
1552
 
1389
- _CODEX_PERIOD_VIEW_CACHE: dict[object, tuple] = {}
1390
- _CODEX_CACHE_REPORT_ROWS: dict[object, tuple] = {}
1391
- _CODEX_SESSION_VIEW_CACHE: dict[object, tuple[object, Any]] = {}
1553
+ _CODEX_PERIOD_VIEW_CACHE: dict[object, tuple] = _ObservedDict()
1554
+ _CODEX_CACHE_REPORT_ROWS: dict[object, tuple] = _ObservedDict()
1555
+ _CODEX_SESSION_VIEW_CACHE: dict[object, tuple[object, Any]] = _ObservedDict()
1392
1556
 
1393
1557
 
1394
1558
  def _codex_session_row_key(row: object) -> tuple[str, str]:
@@ -3577,7 +3741,7 @@ def _alerts_wire(
3577
3741
  )[:SOURCE_HISTORY_LIMIT])
3578
3742
 
3579
3743
 
3580
- _CODEX_PROJECT_LABEL_CACHE: dict[object, dict[str, object]] = {}
3744
+ _CODEX_PROJECT_LABEL_CACHE: dict[object, dict[str, object]] = _ObservedDict()
3581
3745
 
3582
3746
 
3583
3747
  def _cached_project_labeled_entries(
@@ -3661,7 +3825,7 @@ def _projects_wire(
3661
3825
  }
3662
3826
 
3663
3827
 
3664
- _CODEX_PROJECT_WIRE_CACHE: dict[object, tuple] = {}
3828
+ _CODEX_PROJECT_WIRE_CACHE: dict[object, tuple] = _ObservedDict()
3665
3829
 
3666
3830
 
3667
3831
  def _cached_projects_wire(
@@ -3888,7 +4052,7 @@ def _partial_projects_wire(
3888
4052
  }
3889
4053
 
3890
4054
 
3891
- _CODEX_ENTRY_ADAPTER_CACHE: dict[int, tuple[object, CodexEntry]] = {}
4055
+ _CODEX_ENTRY_ADAPTER_CACHE: dict[int, tuple[object, CodexEntry]] = _ObservedDict()
3892
4056
 
3893
4057
 
3894
4058
  def _codex_entries_from_accounting(entries: Iterable[object]) -> list[CodexEntry]:
@@ -4029,7 +4193,7 @@ def _build_codex_native_weekly_view(
4029
4193
  )
4030
4194
 
4031
4195
 
4032
- _CODEX_WEEKLY_VIEW_CACHE: dict[object, tuple] = {}
4196
+ _CODEX_WEEKLY_VIEW_CACHE: dict[object, tuple] = _ObservedDict()
4033
4197
 
4034
4198
 
4035
4199
  def _codex_weekly_period_for_entry(
@@ -4198,6 +4362,9 @@ def _codex_accounts_wire(
4198
4362
  accounting_start: dt.datetime,
4199
4363
  accounting_end: dt.datetime,
4200
4364
  population: tuple[object, ...],
4365
+ population_by_account: "Mapping[str, tuple[object, ...]] | None" = None,
4366
+ account_versions: "Mapping[str, int] | None" = None,
4367
+ population_signature: object | None = None,
4201
4368
  ) -> tuple[
4202
4369
  list[dict[str, object]], list[dict[str, object]], dict[str, object]
4203
4370
  ]:
@@ -4261,7 +4428,11 @@ def _codex_accounts_wire(
4261
4428
  # so the buckets are the sets those filters selected from, and it preserves
4262
4429
  # encounter order, which `_aggregate_codex_buckets` depends on for a
4263
4430
  # bucket's first-seen model order.
4264
- by_account = _codex_partition_by_account(population)
4431
+ by_account = (
4432
+ dict(population_by_account)
4433
+ if population_by_account is not None
4434
+ else _codex_partition_by_account(population)
4435
+ )
4265
4436
 
4266
4437
  def _slice(
4267
4438
  account_key: str,
@@ -4350,13 +4521,35 @@ def _codex_accounts_wire(
4350
4521
  _codex_label_map = _cctally_account.display_label_map(
4351
4522
  context.stats_conn, "codex")
4352
4523
 
4353
- def _totals(rows: tuple[object, ...]) -> dict[str, object]:
4524
+ def _totals(
4525
+ account_key: str,
4526
+ rows: tuple[object, ...],
4527
+ window_signature: object,
4528
+ ) -> dict[str, object]:
4529
+ row_membership = (
4530
+ len(rows),
4531
+ None if not rows else int(
4532
+ getattr(rows[0], "cache_entry_id", 0) or 0),
4533
+ None if not rows else int(
4534
+ getattr(rows[-1], "cache_entry_id", 0) or 0),
4535
+ )
4536
+ signature = (
4537
+ population_signature,
4538
+ int(account_versions.get(account_key, 0)),
4539
+ window_signature,
4540
+ row_membership,
4541
+ context.display_tz_name,
4542
+ context.speed,
4543
+ ) if account_versions is not None and population_signature is not None else None
4544
+ cached = _CODEX_ACCOUNT_CARD_TOTALS_CACHE.get(account_key)
4545
+ if signature is not None and cached is not None and cached[0] == signature:
4546
+ return cached[1]
4354
4547
  entries = _codex_entries_from_accounting(rows)
4355
4548
  cost = build_codex_daily_view(
4356
4549
  entries, now_utc=context.now_utc, tz_name=context.display_tz_name,
4357
4550
  speed=context.speed,
4358
4551
  ).total_cost_usd if entries else 0.0
4359
- return {
4552
+ value = {
4360
4553
  "spendUsd": cost,
4361
4554
  "inputTokens": sum(e.input_tokens for e in entries),
4362
4555
  "cachedInputTokens": sum(e.cached_input_tokens for e in entries),
@@ -4364,6 +4557,20 @@ def _codex_accounts_wire(
4364
4557
  "reasoningOutputTokens": sum(e.reasoning_output_tokens for e in entries),
4365
4558
  "totalTokens": sum(e.total_tokens for e in entries),
4366
4559
  }
4560
+ if signature is not None:
4561
+ global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
4562
+ admitted = (
4563
+ account_key in _CODEX_ACCOUNT_CARD_TOTALS_CACHE
4564
+ or (
4565
+ len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE) + 1
4566
+ ) * _CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES
4567
+ <= _CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES
4568
+ )
4569
+ if admitted:
4570
+ _CODEX_ACCOUNT_CARD_TOTALS_CACHE[account_key] = (signature, value)
4571
+ else:
4572
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS += 1
4573
+ return value
4367
4574
 
4368
4575
  accounts_wire: list[dict[str, object]] = []
4369
4576
  hero_cycles_wire: list[dict[str, object]] = []
@@ -4377,16 +4584,22 @@ def _codex_accounts_wire(
4377
4584
  key, cyc.start_at, cycle_end,
4378
4585
  source_root_keys=cyc.source_root_keys,
4379
4586
  )
4380
- totals = _totals(rows)
4587
+ totals = _totals(key, rows, (
4588
+ "cycle", cyc.start_at, cyc.resets_at,
4589
+ tuple(cyc.source_root_keys)))
4381
4590
  elif is_unattributed:
4382
- totals = _totals(unattributed_window_rows)
4591
+ totals = _totals(
4592
+ key, unattributed_window_rows,
4593
+ ("fallback",),
4594
+ )
4383
4595
  else:
4384
4596
  # A real account without a live weekly cycle: totals over ONE
4385
4597
  # native cycle width ending now, so this card can be summed into a
4386
4598
  # week-labelled headline without overstating it (#564). No bars or
4387
4599
  # reset, because there is no live cycle to describe.
4388
4600
  rows = _slice(key, fallback_start, accounting_end)
4389
- totals = _totals(rows)
4601
+ totals = _totals(
4602
+ key, rows, ("fallback",))
4390
4603
  card: dict[str, object] = {
4391
4604
  "accountKey": key,
4392
4605
  "label": _codex_label_map.get(key) or _cctally_account.account_label(context.stats_conn, key),
@@ -4444,6 +4657,8 @@ def _codex_accounts_wire(
4444
4657
  "cost_usd": totals["spendUsd"],
4445
4658
  "total_tokens": totals["totalTokens"],
4446
4659
  })
4660
+ for stale_key in set(_CODEX_ACCOUNT_CARD_TOTALS_CACHE) - set(ordered_keys):
4661
+ _CODEX_ACCOUNT_CARD_TOTALS_CACHE.pop(stale_key, None)
4447
4662
  return accounts_wire, hero_cycles_wire, {
4448
4663
  "scope": "account_cycles",
4449
4664
  "status": "certified",
@@ -4451,6 +4666,11 @@ def _codex_accounts_wire(
4451
4666
  }
4452
4667
 
4453
4668
 
4669
+ _CODEX_ACCOUNT_CARD_TOTALS_CACHE: dict[
4670
+ str, tuple[object, dict[str, object]]
4671
+ ] = _ObservedDict()
4672
+
4673
+
4454
4674
  def _codex_partition_by_account(
4455
4675
  entries: Iterable[object],
4456
4676
  ) -> dict[str, tuple[object, ...]]:
@@ -4524,14 +4744,254 @@ def _codex_fold_visible_rows(
4524
4744
  )
4525
4745
 
4526
4746
 
4747
+ _CODEX_VISIBLE_POPULATION_MAX_BYTES = 128 * 1024 * 1024
4748
+ _CODEX_VISIBLE_POPULATION_CACHE: dict[str, object] = _ObservedDict()
4749
+ _CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES = 256 * 1024
4750
+ _CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES = 1024
4751
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
4752
+
4753
+
4754
+ def _codex_population_order_key(entry: object) -> tuple[object, ...]:
4755
+ """The accounting carrier's canonical encounter order (#582)."""
4756
+ return (
4757
+ getattr(entry, "timestamp"),
4758
+ str(getattr(entry, "source_root_key", "")),
4759
+ str(getattr(entry, "conversation_key", "")),
4760
+ int(getattr(entry, "cache_entry_id", 0) or 0),
4761
+ )
4762
+
4763
+
4764
+ def _codex_visible_population_estimated_bytes(
4765
+ rows: Iterable[object], entries: Iterable[CodexEntry], account_count: int,
4766
+ ) -> int:
4767
+ """Conservative retained-reference estimate for the one-generation memo.
4768
+
4769
+ Strings and immutable row values are already retained by the accounting
4770
+ carrier or shared by the adapter cache. This estimate charges both object
4771
+ shells, four dict/tuple references per row, and per-account container
4772
+ overhead. It is deliberately stable and cheap enough to compute only on a
4773
+ cold population; its role is a hard admission bound, not heap profiling.
4774
+ """
4775
+ row_values = tuple(rows)
4776
+ adapted_values = tuple(entries)
4777
+ return (
4778
+ sum(sys.getsizeof(row) for row in row_values)
4779
+ + sum(sys.getsizeof(entry) for entry in adapted_values)
4780
+ + (len(row_values) * 8 * 4)
4781
+ + (int(account_count) * 1024)
4782
+ + 4096
4783
+ )
4784
+
4785
+
4786
+ def codex_visible_population_cache_stats() -> Mapping[str, object]:
4787
+ """Observed size/fallback counters for both new source caches."""
4788
+ return MappingProxyType({
4789
+ "estimatedBytes": int(
4790
+ _CODEX_VISIBLE_POPULATION_CACHE.get("estimated_bytes", 0)),
4791
+ "maxBytes": _CODEX_VISIBLE_POPULATION_MAX_BYTES,
4792
+ "entryCount": int(
4793
+ _CODEX_VISIBLE_POPULATION_CACHE.get("entry_count", 0)),
4794
+ "fallbackCount": int(
4795
+ _CODEX_VISIBLE_POPULATION_CACHE.get("fallback_count", 0)),
4796
+ "accountCardEstimatedBytes": (
4797
+ len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE)
4798
+ * _CODEX_ACCOUNT_CARD_TOTAL_ESTIMATED_BYTES
4799
+ ),
4800
+ "accountCardMaxBytes": _CODEX_ACCOUNT_CARD_TOTALS_MAX_BYTES,
4801
+ "accountCardEntryCount": len(_CODEX_ACCOUNT_CARD_TOTALS_CACHE),
4802
+ "accountCardFallbackCount": _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS,
4803
+ })
4804
+
4805
+
4806
+ def _cached_codex_visible_rows(
4807
+ rows: tuple[object, ...],
4808
+ *,
4809
+ changed_old: tuple[object, ...],
4810
+ changed_new: tuple[object, ...],
4811
+ semantic_signature: object,
4812
+ ) -> "tuple[list[CodexEntry], dict[str, tuple[object, ...]], dict[str, tuple[CodexEntry, ...]], dict[str, int]]":
4813
+ """Reuse the immutable visible population, updating only dirty accounts.
4814
+
4815
+ The durable accounting ledger supplies exact old/new path populations. A
4816
+ clean generation therefore returns the prior fold without visiting a row;
4817
+ a dirty generation adapts only ``changed_new`` and rebuilds only accounts
4818
+ named by either side of that delta. The merged encounter order is restored
4819
+ from the authoritative accounting tuple by integer-id lookup, so downstream
4820
+ floating-point accumulation order remains byte-identical.
4821
+
4822
+ Rows without a durable positive id and populations above the byte budget
4823
+ take the cold path and are not retained. Both are complete invalidations,
4824
+ never partial reuse.
4825
+ """
4826
+ state = _CODEX_VISIBLE_POPULATION_CACHE
4827
+ prior_fallbacks = int(state.get("fallback_count", 0))
4828
+ reusable = (
4829
+ state.get("signature") == semantic_signature
4830
+ and isinstance(state.get("rows_by_id"), dict)
4831
+ and isinstance(state.get("entries_by_id"), dict)
4832
+ and all(int(getattr(row, "cache_entry_id", 0) or 0) > 0 for row in rows)
4833
+ and all(int(getattr(row, "cache_entry_id", 0) or 0) > 0
4834
+ for row in (*changed_old, *changed_new))
4835
+ )
4836
+ if reusable and not changed_old and not changed_new:
4837
+ return (
4838
+ list(state["entries"]),
4839
+ dict(state["rows_by_account"]),
4840
+ dict(state["entries_by_account"]),
4841
+ dict(state["account_versions"]),
4842
+ )
4843
+
4844
+ if not reusable:
4845
+ entries, rows_by_account, entries_by_account = _codex_fold_visible_rows(rows)
4846
+ estimated_bytes = _codex_visible_population_estimated_bytes(
4847
+ rows, entries, len(rows_by_account))
4848
+ if (
4849
+ estimated_bytes > _CODEX_VISIBLE_POPULATION_MAX_BYTES
4850
+ or any(int(getattr(row, "cache_entry_id", 0) or 0) <= 0 for row in rows)
4851
+ ):
4852
+ state.clear()
4853
+ state.update({
4854
+ "fallback_count": prior_fallbacks + 1,
4855
+ "estimated_bytes": 0,
4856
+ "entry_count": 0,
4857
+ })
4858
+ return entries, rows_by_account, entries_by_account, {}
4859
+ entry_by_id = {
4860
+ int(getattr(row, "cache_entry_id")): entry
4861
+ for row, entry in zip(rows, entries)
4862
+ }
4863
+ state.clear()
4864
+ state.update({
4865
+ "signature": semantic_signature,
4866
+ "entries": tuple(entries),
4867
+ "rows_by_id": {
4868
+ int(getattr(row, "cache_entry_id")): row for row in rows
4869
+ },
4870
+ "entries_by_id": entry_by_id,
4871
+ "rows_by_account": dict(rows_by_account),
4872
+ "entries_by_account": dict(entries_by_account),
4873
+ "account_versions": {key: 1 for key in rows_by_account},
4874
+ "estimated_bytes": estimated_bytes,
4875
+ "entry_count": len(rows),
4876
+ "account_count": len(rows_by_account),
4877
+ "fallback_count": prior_fallbacks,
4878
+ })
4879
+ return entries, rows_by_account, entries_by_account, dict(
4880
+ state["account_versions"])
4881
+
4882
+ rows_by_id = dict(state["rows_by_id"])
4883
+ entries_by_id = dict(state["entries_by_id"])
4884
+ rows_by_account = dict(state["rows_by_account"])
4885
+ entries_by_account = dict(state["entries_by_account"])
4886
+ account_versions = dict(state["account_versions"])
4887
+ estimated_bytes = int(state.get("estimated_bytes", 0))
4888
+ prior_account_count = int(state.get("account_count", len(rows_by_account)))
4889
+ affected: set[str] = set()
4890
+ old_ids: set[int] = set()
4891
+ for row in changed_old:
4892
+ row_id = int(getattr(row, "cache_entry_id"))
4893
+ old_ids.add(row_id)
4894
+ affected.add(str(
4895
+ getattr(row, "account_key", "") or _lib_accounts.UNATTRIBUTED))
4896
+ prior_entry = entries_by_id.get(row_id)
4897
+ estimated_bytes -= (
4898
+ sys.getsizeof(row)
4899
+ + (0 if prior_entry is None else sys.getsizeof(prior_entry))
4900
+ + (8 * 4)
4901
+ )
4902
+ rows_by_id.pop(row_id, None)
4903
+ entries_by_id.pop(row_id, None)
4904
+ converted_new = _codex_entries_from_accounting(changed_new)
4905
+ for row, converted in zip(changed_new, converted_new):
4906
+ row_id = int(getattr(row, "cache_entry_id"))
4907
+ key = str(
4908
+ getattr(row, "account_key", "") or _lib_accounts.UNATTRIBUTED)
4909
+ affected.add(key)
4910
+ estimated_bytes += (
4911
+ sys.getsizeof(row) + sys.getsizeof(converted) + (8 * 4)
4912
+ )
4913
+ rows_by_id[row_id] = row
4914
+ entries_by_id[row_id] = converted
4915
+
4916
+ for key in affected:
4917
+ retained_rows = [
4918
+ row for row in rows_by_account.get(key, ())
4919
+ if int(getattr(row, "cache_entry_id")) not in old_ids
4920
+ ]
4921
+ retained_entries = {
4922
+ int(getattr(entry, "cache_entry_id")): entry
4923
+ for entry in entries_by_account.get(key, ())
4924
+ if int(getattr(entry, "cache_entry_id")) not in old_ids
4925
+ }
4926
+ for row, converted in zip(changed_new, converted_new):
4927
+ if str(getattr(row, "account_key", "") or
4928
+ _lib_accounts.UNATTRIBUTED) == key:
4929
+ retained_rows.append(row)
4930
+ retained_entries[int(getattr(row, "cache_entry_id"))] = converted
4931
+ retained_rows.sort(key=_codex_population_order_key)
4932
+ if retained_rows:
4933
+ rows_by_account[key] = tuple(retained_rows)
4934
+ entries_by_account[key] = tuple(
4935
+ retained_entries[int(getattr(row, "cache_entry_id"))]
4936
+ for row in retained_rows
4937
+ )
4938
+ else:
4939
+ rows_by_account.pop(key, None)
4940
+ entries_by_account.pop(key, None)
4941
+ account_versions[key] = int(account_versions.get(key, 0)) + 1
4942
+
4943
+ try:
4944
+ entries = tuple(
4945
+ entries_by_id[int(getattr(row, "cache_entry_id"))] for row in rows
4946
+ )
4947
+ except KeyError:
4948
+ # An inconsistent delta must fail closed to the exact cold fold.
4949
+ state.clear()
4950
+ return _cached_codex_visible_rows(
4951
+ rows,
4952
+ changed_old=rows,
4953
+ changed_new=rows,
4954
+ semantic_signature=semantic_signature,
4955
+ )
4956
+ estimated_bytes += max(0, len(rows_by_account) - prior_account_count) * 1024
4957
+ estimated_bytes = max(0, estimated_bytes)
4958
+ if estimated_bytes > _CODEX_VISIBLE_POPULATION_MAX_BYTES:
4959
+ state.clear()
4960
+ state.update({
4961
+ "fallback_count": prior_fallbacks + 1,
4962
+ "estimated_bytes": 0,
4963
+ "entry_count": 0,
4964
+ })
4965
+ else:
4966
+ state.clear()
4967
+ state.update({
4968
+ "signature": semantic_signature,
4969
+ "entries": entries,
4970
+ "rows_by_id": rows_by_id,
4971
+ "entries_by_id": entries_by_id,
4972
+ "rows_by_account": rows_by_account,
4973
+ "entries_by_account": entries_by_account,
4974
+ "account_versions": account_versions,
4975
+ "estimated_bytes": estimated_bytes,
4976
+ "entry_count": len(rows),
4977
+ "account_count": len(rows_by_account),
4978
+ "fallback_count": prior_fallbacks,
4979
+ })
4980
+ return list(entries), rows_by_account, entries_by_account, account_versions
4981
+
4982
+
4527
4983
  _CODEX_ACCOUNT_SCOPE_CACHE: dict[
4528
4984
  str, tuple[object, dict[str, object]]
4529
- ] = {}
4985
+ ] = _ObservedDict()
4530
4986
 
4531
4987
 
4532
4988
  def reset_codex_account_scope_cache() -> None:
4533
4989
  """Test/process reset for #582's immutable finalized account scopes."""
4990
+ global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
4534
4991
  _CODEX_ACCOUNT_SCOPE_CACHE.clear()
4992
+ _CODEX_VISIBLE_POPULATION_CACHE.clear()
4993
+ _CODEX_ACCOUNT_CARD_TOTALS_CACHE.clear()
4994
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
4535
4995
  _CODEX_ENTRY_ADAPTER_CACHE.clear()
4536
4996
  _CODEX_PROJECT_LABEL_CACHE.clear()
4537
4997
  _CODEX_PERIOD_VIEW_CACHE.clear()
@@ -5058,11 +5518,343 @@ def _codex_source_caches() -> tuple[dict, ...]:
5058
5518
  _CODEX_PROJECT_LABEL_CACHE,
5059
5519
  _CODEX_PROJECT_WIRE_CACHE,
5060
5520
  _CODEX_ENTRY_ADAPTER_CACHE,
5521
+ _CODEX_VISIBLE_POPULATION_CACHE,
5522
+ _CODEX_ACCOUNT_CARD_TOTALS_CACHE,
5061
5523
  _CODEX_WEEKLY_VIEW_CACHE,
5062
5524
  _CODEX_ACCOUNT_SCOPE_CACHE,
5063
5525
  )
5064
5526
 
5065
5527
 
5528
+ _CODEX_SOURCE_ACCELERATOR_MAX_BYTES = 768 * 1024 * 1024
5529
+ _CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES = 500_000
5530
+ _CODEX_SOURCE_ACCELERATOR_STATS_LOCK = threading.Lock()
5531
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
5532
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
5533
+ _CODEX_SOURCE_ACCELERATOR_EVICTIONS = 0
5534
+ _CODEX_SOURCE_ACCELERATOR_FALLBACKS = 0
5535
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = -1
5536
+ _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS = 0
5537
+ _CODEX_SOURCE_MEMORY_COMPLETION_LOCK = None
5538
+
5539
+
5540
+ class _CodexSourceMemoryWorker:
5541
+ """One latest-wins, cancellable retained-size verifier.
5542
+
5543
+ The source caches are shallow-copied at a successful build boundary. The
5544
+ worker traverses that immutable container snapshot so its multi-second
5545
+ production-scale admission pass neither blocks the five-second publisher
5546
+ nor races a live dict mutation. A newer generation cancels the old walk;
5547
+ shutdown uses the same cooperative cancellation path.
5548
+ """
5549
+
5550
+ def __init__(self) -> None:
5551
+ self._condition = threading.Condition()
5552
+ self._pending: tuple[int, tuple[dict, ...]] | None = None
5553
+ self._result: tuple[int, int | None, str | None] | None = None
5554
+ self._latest_generation = -1
5555
+ self._active_generation: int | None = None
5556
+ self._stop = False
5557
+ self._thread: threading.Thread | None = None
5558
+ self._duty_wall = 0.0
5559
+ self._duty_cpu = 0.0
5560
+
5561
+ def submit(self, generation: int, snapshot: tuple[dict, ...]) -> None:
5562
+ with self._condition:
5563
+ if self._stop:
5564
+ return
5565
+ self._latest_generation = int(generation)
5566
+ self._pending = (int(generation), snapshot)
5567
+ if self._thread is None or not self._thread.is_alive():
5568
+ self._thread = threading.Thread(
5569
+ target=self._run,
5570
+ name="cctally-source-memory",
5571
+ daemon=True,
5572
+ )
5573
+ self._thread.start()
5574
+ self._condition.notify()
5575
+
5576
+ def take_result(self) -> tuple[int, int | None, str | None] | None:
5577
+ with self._condition:
5578
+ result = self._result
5579
+ self._result = None
5580
+ return result
5581
+
5582
+ def _cooperate(self) -> bool:
5583
+ with self._condition:
5584
+ cancelled = (
5585
+ self._stop
5586
+ or self._active_generation != self._latest_generation
5587
+ )
5588
+ if cancelled:
5589
+ return True
5590
+ cpu_elapsed = time.thread_time() - self._duty_cpu
5591
+ wall_elapsed = time.monotonic() - self._duty_wall
5592
+ delay = (cpu_elapsed / RETAINED_SIZE_WORKER_DUTY) - wall_elapsed
5593
+ if delay > 0:
5594
+ time.sleep(min(delay, 0.01))
5595
+ with self._condition:
5596
+ return (
5597
+ self._stop
5598
+ or self._active_generation != self._latest_generation
5599
+ )
5600
+
5601
+ def _run(self) -> None:
5602
+ while True:
5603
+ with self._condition:
5604
+ while self._pending is None and not self._stop:
5605
+ self._condition.wait()
5606
+ if self._stop:
5607
+ return
5608
+ generation, snapshot = self._pending
5609
+ self._pending = None
5610
+ self._active_generation = generation
5611
+ estimated: int | None = None
5612
+ error: str | None = None
5613
+ entry_count = sum(len(cache) for cache in snapshot)
5614
+ try:
5615
+ while not RETAINED_SIZE_WORK_LOCK.acquire(timeout=0.05):
5616
+ if self._cooperate():
5617
+ raise RetainedSizeCancelled()
5618
+ try:
5619
+ self._duty_wall = time.monotonic()
5620
+ self._duty_cpu = time.thread_time()
5621
+ estimated = retained_size_bytes(
5622
+ snapshot,
5623
+ stop_after=_CODEX_SOURCE_ACCELERATOR_MAX_BYTES,
5624
+ cancelled=self._cooperate,
5625
+ )
5626
+ finally:
5627
+ RETAINED_SIZE_WORK_LOCK.release()
5628
+ except RetainedSizeCancelled:
5629
+ continue
5630
+ except Exception as exc: # noqa: BLE001 - keep the sole worker alive
5631
+ error = f"{type(exc).__name__}: {exc}"
5632
+ finally:
5633
+ snapshot = ()
5634
+ published = (
5635
+ error is None
5636
+ and estimated is not None
5637
+ and _publish_codex_source_measurement(
5638
+ generation, int(estimated), entry_count)
5639
+ )
5640
+ enforced = False
5641
+ completion_lock = _CODEX_SOURCE_MEMORY_COMPLETION_LOCK
5642
+ if not published and completion_lock is not None:
5643
+ with completion_lock:
5644
+ enforced = _enforce_codex_source_unsafe_completion(
5645
+ generation,
5646
+ None if estimated is None else int(estimated),
5647
+ error,
5648
+ )
5649
+ with self._condition:
5650
+ self._active_generation = None
5651
+ if (
5652
+ not published and not enforced
5653
+ and not self._stop
5654
+ and generation == self._latest_generation
5655
+ ):
5656
+ self._result = (
5657
+ generation,
5658
+ None if estimated is None else int(estimated),
5659
+ error,
5660
+ )
5661
+
5662
+ def shutdown(self) -> bool:
5663
+ with self._condition:
5664
+ self._stop = True
5665
+ self._pending = None
5666
+ thread = self._thread
5667
+ self._condition.notify_all()
5668
+ if thread is not None:
5669
+ thread.join(timeout=1.0)
5670
+ return thread is None or not thread.is_alive()
5671
+
5672
+ def is_alive(self) -> bool:
5673
+ with self._condition:
5674
+ return self._thread is not None and self._thread.is_alive()
5675
+
5676
+
5677
+ _CODEX_SOURCE_MEMORY_WORKER = _CodexSourceMemoryWorker()
5678
+
5679
+
5680
+ def set_codex_source_memory_completion_lock(lock) -> None:
5681
+ """Bind unsafe verifier completions to the dashboard publisher lock."""
5682
+ global _CODEX_SOURCE_MEMORY_COMPLETION_LOCK
5683
+ _CODEX_SOURCE_MEMORY_COMPLETION_LOCK = lock
5684
+
5685
+
5686
+ def _enforce_codex_source_unsafe_completion(
5687
+ generation: int, estimated: int | None, error: str | None,
5688
+ ) -> bool:
5689
+ """Evict a current over-cap/error result without awaiting another tick."""
5690
+ if error is None and (
5691
+ estimated is None or estimated <= _CODEX_SOURCE_ACCELERATOR_MAX_BYTES
5692
+ ):
5693
+ return False
5694
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
5695
+ if (
5696
+ _CODEX_SOURCE_ACCELERATOR_DIRTY
5697
+ or generation != _CODEX_SOURCE_ACCELERATOR_GENERATION
5698
+ ):
5699
+ return False
5700
+ caches = _codex_source_caches()
5701
+ entry_count = sum(len(cache) for cache in caches)
5702
+ global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
5703
+ global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
5704
+ global _CODEX_SOURCE_ACCELERATOR_EVICTIONS
5705
+ global _CODEX_SOURCE_ACCELERATOR_FALLBACKS
5706
+ global _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS
5707
+ with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
5708
+ reset_codex_quota_observation_cache()
5709
+ for cache in caches[1:]:
5710
+ cache.clear()
5711
+ _CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
5712
+ _CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
5713
+ if error is not None:
5714
+ _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS += 1
5715
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
5716
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
5717
+ return True
5718
+
5719
+
5720
+ def _publish_codex_source_measurement(
5721
+ generation: int, estimated: int, entry_count: int,
5722
+ ) -> bool:
5723
+ """Publish an under-cap current generation without waiting for a tick."""
5724
+ if estimated > _CODEX_SOURCE_ACCELERATOR_MAX_BYTES:
5725
+ return False
5726
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
5727
+ if (
5728
+ _CODEX_SOURCE_ACCELERATOR_DIRTY
5729
+ or generation != _CODEX_SOURCE_ACCELERATOR_GENERATION
5730
+ ):
5731
+ return False
5732
+ global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
5733
+ global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
5734
+ global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
5735
+ with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
5736
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = int(estimated)
5737
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
5738
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = int(generation)
5739
+ return True
5740
+
5741
+
5742
+ def _take_codex_source_accelerator_dirty() -> int | None:
5743
+ """Claim one dirty generation for a serialized source-build admission."""
5744
+ global _CODEX_SOURCE_ACCELERATOR_DIRTY
5745
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
5746
+ if not _CODEX_SOURCE_ACCELERATOR_DIRTY:
5747
+ return None
5748
+ _CODEX_SOURCE_ACCELERATOR_DIRTY = False
5749
+ return int(_CODEX_SOURCE_ACCELERATOR_GENERATION)
5750
+
5751
+
5752
+ def _mark_codex_source_accelerators_clean() -> None:
5753
+ global _CODEX_SOURCE_ACCELERATOR_DIRTY
5754
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
5755
+ _CODEX_SOURCE_ACCELERATOR_DIRTY = False
5756
+
5757
+
5758
+ def enforce_codex_source_accelerator_bounds() -> None:
5759
+ """Schedule byte admission and apply completed results without cadence I/O."""
5760
+ caches = _codex_source_caches()
5761
+ entry_count = sum(len(cache) for cache in caches)
5762
+ global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
5763
+ global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
5764
+ global _CODEX_SOURCE_ACCELERATOR_EVICTIONS
5765
+ global _CODEX_SOURCE_ACCELERATOR_FALLBACKS
5766
+ global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
5767
+ global _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS
5768
+ completed = _CODEX_SOURCE_MEMORY_WORKER.take_result()
5769
+ with _CODEX_SOURCE_ACCELERATOR_DIRTY_LOCK:
5770
+ current_generation = int(_CODEX_SOURCE_ACCELERATOR_GENERATION)
5771
+ currently_dirty = bool(_CODEX_SOURCE_ACCELERATOR_DIRTY)
5772
+ measured = (
5773
+ None if completed is None or completed[1] is None
5774
+ else int(completed[1])
5775
+ )
5776
+ measured_generation = -1 if completed is None else int(completed[0])
5777
+ measurement_error = None if completed is None else completed[2]
5778
+ measurement_is_current = (
5779
+ completed is not None
5780
+ and measured_generation == current_generation
5781
+ and not currently_dirty
5782
+ )
5783
+ with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
5784
+ if measurement_is_current and measurement_error is not None:
5785
+ reset_codex_quota_observation_cache()
5786
+ for cache in caches[1:]:
5787
+ cache.clear()
5788
+ _CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
5789
+ _CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
5790
+ _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS += 1
5791
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
5792
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
5793
+ elif (
5794
+ (measurement_is_current
5795
+ and measured is not None
5796
+ and measured > _CODEX_SOURCE_ACCELERATOR_MAX_BYTES)
5797
+ or entry_count > _CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES
5798
+ ):
5799
+ # The caller already owns the completed SourceDashboardState. A
5800
+ # cold next build preserves wire semantics and prevents any one
5801
+ # history/account/root churn pattern from growing process state.
5802
+ reset_codex_quota_observation_cache()
5803
+ for cache in caches[1:]:
5804
+ cache.clear()
5805
+ _CODEX_SOURCE_ACCELERATOR_EVICTIONS += entry_count
5806
+ _CODEX_SOURCE_ACCELERATOR_FALLBACKS += 1
5807
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
5808
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
5809
+ elif measured is not None:
5810
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = measured
5811
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
5812
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = measured_generation
5813
+ else:
5814
+ # Entry count is exact immediately; bytes remain the last complete
5815
+ # generation until the latest-wins verifier publishes a result.
5816
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = int(entry_count)
5817
+
5818
+ generation = _take_codex_source_accelerator_dirty()
5819
+ if generation is not None:
5820
+ _CODEX_SOURCE_MEMORY_WORKER.submit(
5821
+ generation, tuple(dict(cache) for cache in caches))
5822
+
5823
+
5824
+ def shutdown_codex_source_memory_worker() -> None:
5825
+ """Cancel and join the one aggregate verifier during dashboard shutdown."""
5826
+ global _CODEX_SOURCE_MEMORY_WORKER
5827
+ worker = _CODEX_SOURCE_MEMORY_WORKER
5828
+ if not worker.shutdown():
5829
+ raise RuntimeError("Codex source memory verifier did not stop")
5830
+ _CODEX_SOURCE_MEMORY_WORKER = _CodexSourceMemoryWorker()
5831
+
5832
+
5833
+ def codex_source_accelerator_memory_stats() -> Mapping[str, int]:
5834
+ """Safe aggregate diagnostics for every source-build accelerator."""
5835
+ with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
5836
+ return MappingProxyType({
5837
+ "estimatedBytes": int(_CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES),
5838
+ "maxBytes": int(_CODEX_SOURCE_ACCELERATOR_MAX_BYTES),
5839
+ "entryCount": int(_CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT),
5840
+ "maxEntries": int(_CODEX_SOURCE_ACCELERATOR_MAX_ENTRIES),
5841
+ "evictionCount": int(_CODEX_SOURCE_ACCELERATOR_EVICTIONS),
5842
+ "fallbackCount": int(_CODEX_SOURCE_ACCELERATOR_FALLBACKS),
5843
+ "measurementErrorCount": int(
5844
+ _CODEX_SOURCE_ACCELERATOR_MEASUREMENT_ERRORS),
5845
+ "measuredGeneration": int(
5846
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION),
5847
+ "currentGeneration": int(_CODEX_SOURCE_ACCELERATOR_GENERATION),
5848
+ "measurementPending": int(
5849
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
5850
+ != _CODEX_SOURCE_ACCELERATOR_GENERATION
5851
+ ),
5852
+ "workerAlive": int(
5853
+ getattr(_CODEX_SOURCE_MEMORY_WORKER, "is_alive", lambda: False)()
5854
+ ),
5855
+ })
5856
+
5857
+
5066
5858
  def reset_codex_source_caches() -> None:
5067
5859
  """Discard every reuse a Codex source build could draw on.
5068
5860
 
@@ -5071,9 +5863,21 @@ def reset_codex_source_caches() -> None:
5071
5863
  cache state too, which lives in ``_lib_snapshot_cache`` rather than in a
5072
5864
  dict here and is the other half of what a build retains.
5073
5865
  """
5074
- for cache in _codex_source_caches():
5866
+ global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
5867
+ global _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES
5868
+ global _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT
5869
+ global _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION
5870
+ reset_codex_quota_observation_cache()
5871
+ for cache in _codex_source_caches()[1:]:
5075
5872
  cache.clear()
5873
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = 0
5076
5874
  _lib_snapshot_cache.reset_codex_accounting_cache_state()
5875
+ with _CODEX_SOURCE_ACCELERATOR_STATS_LOCK:
5876
+ _CODEX_SOURCE_ACCELERATOR_ESTIMATED_BYTES = 0
5877
+ _CODEX_SOURCE_ACCELERATOR_ENTRY_COUNT = 0
5878
+ _mark_codex_source_accelerators_clean()
5879
+ _CODEX_SOURCE_ACCELERATOR_MEASURED_GENERATION = (
5880
+ _CODEX_SOURCE_ACCELERATOR_GENERATION)
5077
5881
 
5078
5882
 
5079
5883
  @dataclass(frozen=True)
@@ -5110,6 +5914,7 @@ def _capture_codex_accounting(
5110
5914
  context.speed,
5111
5915
  tuple(str(root) for root in path_scope.roots),
5112
5916
  active_roots,
5917
+ _cache_database_identity(context.cache_conn),
5113
5918
  ),
5114
5919
  load_all=lambda: load_qualified_codex_entries(
5115
5920
  accounting_start,
@@ -5203,13 +6008,20 @@ class CodexSourceCapture:
5203
6008
  card_population: tuple[object, ...]
5204
6009
  breakdowns: Mapping
5205
6010
  cache_checkpoint: tuple[dict, ...]
6011
+ quota_cache_stats_checkpoint: tuple[dict, int, int, int]
6012
+ account_card_fallbacks_checkpoint: int
5206
6013
  accounting_checkpoint: object
5207
6014
 
5208
6015
 
5209
6016
  def _restore_codex_source_capture(capture: CodexSourceCapture) -> None:
6017
+ global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
5210
6018
  for cache, prior in zip(_codex_source_caches(), capture.cache_checkpoint):
5211
6019
  cache.clear()
5212
6020
  cache.update(prior)
6021
+ _restore_quota_observation_cache_checkpoint(
6022
+ capture.quota_cache_stats_checkpoint)
6023
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = (
6024
+ capture.account_card_fallbacks_checkpoint)
5213
6025
  _lib_snapshot_cache.restore_codex_accounting_cache_state(
5214
6026
  capture.accounting_checkpoint)
5215
6027
 
@@ -5220,8 +6032,11 @@ def capture_codex_source_state(
5220
6032
  path_scope: object,
5221
6033
  ) -> CodexSourceCapture:
5222
6034
  """Capture Codex cache evidence without constructing public view models."""
6035
+ global _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
5223
6036
  caches = _codex_source_caches()
5224
6037
  cache_checkpoint = tuple(dict(cache) for cache in caches)
6038
+ quota_cache_stats_checkpoint = _quota_observation_cache_checkpoint()
6039
+ account_card_fallbacks_checkpoint = _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS
5225
6040
  accounting_checkpoint = (
5226
6041
  _lib_snapshot_cache.checkpoint_codex_accounting_cache_state()
5227
6042
  )
@@ -5231,19 +6046,21 @@ def capture_codex_source_state(
5231
6046
  "SELECT source_root_key FROM codex_source_roots"
5232
6047
  )
5233
6048
  ))
5234
- quota_observations = tuple(_cached_codex_quota_observations(
5235
- source_root_keys=active_roots,
5236
- cache_conn=context.cache_conn,
5237
- stats_conn=context.stats_conn,
5238
- stats_identity=context.stats_identity,
5239
- memoize=False,
5240
- captured_at_or_after=(
5241
- context.now_utc - dt.timedelta(days=DASHBOARD_QUOTA_RECENT_DAYS)
5242
- ),
5243
- active_at=context.now_utc,
5244
- max_rows=DASHBOARD_QUOTA_OBSERVATION_LIMIT,
5245
- ))
5246
- projection_incoherent = not codex_projection_coherence(context).coherent
6049
+ with _lib_perf.phase("source.quota"):
6050
+ quota_observations = tuple(_cached_codex_quota_observations(
6051
+ source_root_keys=active_roots,
6052
+ cache_conn=context.cache_conn,
6053
+ stats_conn=context.stats_conn,
6054
+ stats_identity=context.stats_identity,
6055
+ memoize=False,
6056
+ captured_at_or_after=(
6057
+ context.now_utc - dt.timedelta(days=DASHBOARD_QUOTA_RECENT_DAYS)
6058
+ ),
6059
+ active_at=context.now_utc,
6060
+ max_rows=DASHBOARD_QUOTA_OBSERVATION_LIMIT,
6061
+ ))
6062
+ with _lib_perf.phase("source.projection"):
6063
+ projection_incoherent = not codex_projection_coherence(context).coherent
5247
6064
  ingest_backlog = _codex_ingest_backlog_wire(context.cache_conn)
5248
6065
  accounting_end = context.now_utc + dt.timedelta(microseconds=1)
5249
6066
  accounting_start = context.range_start
@@ -5256,19 +6073,20 @@ def capture_codex_source_state(
5256
6073
  _warn_codex_budget_window_once("accounting_range")
5257
6074
  else:
5258
6075
  accounting_start = min(accounting_start, budget_start)
5259
- health = load_codex_project_metadata_health(
5260
- cache_conn=context.cache_conn,
5261
- start=accounting_start,
5262
- end=accounting_end,
5263
- )
5264
- accounting = _capture_codex_accounting(
5265
- context,
5266
- accounting_start=accounting_start,
5267
- accounting_end=accounting_end,
5268
- active_roots=active_roots,
5269
- path_scope=path_scope,
5270
- metadata_incomplete=health.incomplete_rows > 0,
5271
- )
6076
+ with _lib_perf.phase("source.accounting"):
6077
+ health = load_codex_project_metadata_health(
6078
+ cache_conn=context.cache_conn,
6079
+ start=accounting_start,
6080
+ end=accounting_end,
6081
+ )
6082
+ accounting = _capture_codex_accounting(
6083
+ context,
6084
+ accounting_start=accounting_start,
6085
+ accounting_end=accounting_end,
6086
+ active_roots=active_roots,
6087
+ path_scope=path_scope,
6088
+ metadata_incomplete=health.incomplete_rows > 0,
6089
+ )
5272
6090
  try:
5273
6091
  accounting_exists = has_cached_codex_accounting_entries(
5274
6092
  cache_conn=context.cache_conn)
@@ -5281,12 +6099,13 @@ def capture_codex_source_state(
5281
6099
  accounting_exists_error = exc
5282
6100
  conversation_metadata = MappingProxyType(
5283
6101
  _codex_conversation_metadata(context.cache_conn))
5284
- try:
5285
- import _cctally_account
5286
- decorated = _cctally_account.provider_is_decorated(
5287
- context.stats_conn, "codex")
5288
- except Exception:
5289
- decorated = False
6102
+ with _lib_perf.phase("source.accounts"):
6103
+ try:
6104
+ import _cctally_account
6105
+ decorated = _cctally_account.provider_is_decorated(
6106
+ context.stats_conn, "codex")
6107
+ except Exception:
6108
+ decorated = False
5290
6109
  card_population_start = accounting_start
5291
6110
  if decorated:
5292
6111
  card_population_start = min(
@@ -5318,8 +6137,9 @@ def capture_codex_source_state(
5318
6137
  cache_conn=context.cache_conn,
5319
6138
  )
5320
6139
  )
5321
- breakdowns = _capture_quota_breakdown_evidence(
5322
- context, quota_observations)
6140
+ with _lib_perf.phase("source.models"):
6141
+ breakdowns = _capture_quota_breakdown_evidence(
6142
+ context, quota_observations)
5323
6143
  return CodexSourceCapture(
5324
6144
  context=context,
5325
6145
  active_roots=active_roots,
@@ -5337,12 +6157,19 @@ def capture_codex_source_state(
5337
6157
  card_population=tuple(card_population),
5338
6158
  breakdowns=breakdowns,
5339
6159
  cache_checkpoint=cache_checkpoint,
6160
+ quota_cache_stats_checkpoint=quota_cache_stats_checkpoint,
6161
+ account_card_fallbacks_checkpoint=(
6162
+ account_card_fallbacks_checkpoint),
5340
6163
  accounting_checkpoint=accounting_checkpoint,
5341
6164
  )
5342
6165
  except Exception:
5343
6166
  for cache, prior in zip(caches, cache_checkpoint):
5344
6167
  cache.clear()
5345
6168
  cache.update(prior)
6169
+ _restore_quota_observation_cache_checkpoint(
6170
+ quota_cache_stats_checkpoint)
6171
+ _CODEX_ACCOUNT_CARD_TOTALS_FALLBACKS = (
6172
+ account_card_fallbacks_checkpoint)
5346
6173
  _lib_snapshot_cache.restore_codex_accounting_cache_state(
5347
6174
  accounting_checkpoint)
5348
6175
  raise
@@ -5356,12 +6183,15 @@ def build_codex_source_state_from_capture(
5356
6183
  ) -> SourceDashboardState:
5357
6184
  """Fold one captured Codex generation without issuing cache SQL."""
5358
6185
  try:
5359
- return _build_codex_source_state(
5360
- capture.context,
5361
- data_version=data_version,
5362
- path_scope=path_scope,
5363
- capture=capture,
5364
- )
6186
+ with _lib_perf.phase("source.serialization"):
6187
+ result = _build_codex_source_state(
6188
+ capture.context,
6189
+ data_version=data_version,
6190
+ path_scope=path_scope,
6191
+ capture=capture,
6192
+ )
6193
+ enforce_codex_source_accelerator_bounds()
6194
+ return result
5365
6195
  except Exception:
5366
6196
  _restore_codex_source_capture(capture)
5367
6197
  raise
@@ -5558,7 +6388,6 @@ def _build_codex_source_state(
5558
6388
  accounting_changed_new = accounting_capture.changed_new
5559
6389
  accounting_entries = accounting_capture.entries
5560
6390
  metadata_incomplete = accounting_capture.metadata_incomplete
5561
- budget_entries = _codex_entries_from_accounting(accounting_entries)
5562
6391
  cycles_all: list[CodexCycleBoundary] = []
5563
6392
  try:
5564
6393
  # Per-account list (#341 Task 2). ``cycles_all`` drives the per-account
@@ -5612,15 +6441,15 @@ def _build_codex_source_state(
5612
6441
  display_tz_name=context.display_tz_name,
5613
6442
  speed=context.speed,
5614
6443
  )
5615
- visible_accounting_entries = tuple(
5616
- entry for entry in accounting_entries
5617
- if context.range_start <= getattr(entry, "timestamp").astimezone(UTC) < accounting_end
5618
- )
5619
- # #566 §5.1 item 2: one pass over the visible rows produces the merged
5620
- # population and both per-account partitions. The children below reuse
5621
- # these instead of re-partitioning and re-adapting the same rows.
5622
- entries, visible_rows_by_account, visible_entries_by_account = (
5623
- _codex_fold_visible_rows(visible_accounting_entries)
6444
+ visible_accounting_entries = (
6445
+ accounting_entries
6446
+ if accounting_start == context.range_start
6447
+ else tuple(
6448
+ entry for entry in accounting_entries
6449
+ if context.range_start
6450
+ <= getattr(entry, "timestamp").astimezone(UTC)
6451
+ < accounting_end
6452
+ )
5624
6453
  )
5625
6454
  changed_old_visible = tuple(
5626
6455
  entry for entry in accounting_changed_old
@@ -5630,6 +6459,51 @@ def _build_codex_source_state(
5630
6459
  entry for entry in accounting_changed_new
5631
6460
  if context.range_start <= entry.timestamp.astimezone(UTC) < accounting_end
5632
6461
  )
6462
+ population_signature = (
6463
+ "codex-visible-population-v1",
6464
+ _cache_database_identity(context.cache_conn),
6465
+ context.range_start,
6466
+ metadata_incomplete,
6467
+ context.speed,
6468
+ active_roots,
6469
+ )
6470
+ # #681: the durable path delta updates only the affected account partitions.
6471
+ # A quota/stat-only generation returns the prior fold directly. The parent
6472
+ # tuple is restored in authoritative accounting encounter order, preserving
6473
+ # every downstream floating-point and first-seen-model byte.
6474
+ if metadata_incomplete:
6475
+ # The cache-only carrier has no durable old/new path population. Its
6476
+ # full reload is therefore also the presentation fold: no process memo
6477
+ # may interpret an empty delta as proof that an id-stable update did
6478
+ # not happen. Every downstream aggregate cache has the same evidence
6479
+ # gap, so use the established complete accounting reset rather than
6480
+ # clearing only the two newest generations: period, weekly, project,
6481
+ # session, cache-report and adapter memos would otherwise retain an
6482
+ # id-stable update. Returning to qualified reads also starts cold.
6483
+ reset_codex_account_scope_cache()
6484
+ (
6485
+ entries,
6486
+ visible_rows_by_account,
6487
+ visible_entries_by_account,
6488
+ ) = _codex_fold_visible_rows(visible_accounting_entries)
6489
+ visible_account_versions: dict[str, int] = {}
6490
+ else:
6491
+ (
6492
+ entries,
6493
+ visible_rows_by_account,
6494
+ visible_entries_by_account,
6495
+ visible_account_versions,
6496
+ ) = _cached_codex_visible_rows(
6497
+ visible_accounting_entries,
6498
+ changed_old=changed_old_visible,
6499
+ changed_new=changed_new_visible,
6500
+ semantic_signature=population_signature,
6501
+ )
6502
+ budget_entries = (
6503
+ entries
6504
+ if accounting_start == context.range_start
6505
+ else _codex_entries_from_accounting(accounting_entries)
6506
+ )
5633
6507
  changed_old_entries = tuple(_codex_entries_from_accounting(changed_old_visible))
5634
6508
  changed_new_entries = tuple(_codex_entries_from_accounting(changed_new_visible))
5635
6509
 
@@ -5903,6 +6777,26 @@ def _build_codex_source_state(
5903
6777
  accounting_start=accounting_start,
5904
6778
  accounting_end=accounting_end,
5905
6779
  population=card_population,
6780
+ population_by_account=(
6781
+ visible_rows_by_account
6782
+ if card_population is accounting_entries
6783
+ and accounting_start == context.range_start
6784
+ else None
6785
+ ),
6786
+ account_versions=(
6787
+ visible_account_versions
6788
+ if card_population is accounting_entries
6789
+ and accounting_start == context.range_start
6790
+ and visible_account_versions
6791
+ else None
6792
+ ),
6793
+ population_signature=(
6794
+ population_signature
6795
+ if card_population is accounting_entries
6796
+ and accounting_start == context.range_start
6797
+ and visible_account_versions
6798
+ else None
6799
+ ),
5906
6800
  )
5907
6801
  # #416 §5.3: the per-account CHILDREN beside the merged parent. The
5908
6802
  # scope set is exactly the card set, so every chip the client can