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.
Files changed (97) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +4 -6
  3. package/bin/_cctally_account.py +32 -8
  4. package/bin/_cctally_cache.py +6035 -722
  5. package/bin/_cctally_completion.py +1287 -0
  6. package/bin/_cctally_config.py +37 -0
  7. package/bin/_cctally_core.py +400 -2
  8. package/bin/_cctally_dashboard.py +98 -20
  9. package/bin/_cctally_dashboard_conversation.py +292 -23
  10. package/bin/_cctally_dashboard_envelope.py +16 -3
  11. package/bin/_cctally_dashboard_perf.py +128 -9
  12. package/bin/_cctally_dashboard_sources.py +285 -213
  13. package/bin/_cctally_db.py +1177 -23
  14. package/bin/_cctally_diagnosis_sources.py +22 -4
  15. package/bin/_cctally_doctor.py +572 -45
  16. package/bin/_cctally_five_hour.py +84 -33
  17. package/bin/_cctally_forecast.py +108 -15
  18. package/bin/_cctally_journal.py +3950 -116
  19. package/bin/_cctally_journal_repair.py +101 -12
  20. package/bin/_cctally_milestone_history.py +82 -9
  21. package/bin/_cctally_parser.py +42 -0
  22. package/bin/_cctally_percent_breakdown.py +3 -2
  23. package/bin/_cctally_project.py +8 -2
  24. package/bin/_cctally_quarantine.py +652 -0
  25. package/bin/_cctally_quota.py +227 -37
  26. package/bin/_cctally_quota_model.py +360 -83
  27. package/bin/_cctally_record.py +691 -149
  28. package/bin/_cctally_rederive.py +320 -41
  29. package/bin/_cctally_refresh.py +84 -13
  30. package/bin/_cctally_source_analytics.py +83 -17
  31. package/bin/_cctally_statusline.py +6154 -517
  32. package/bin/_cctally_store.py +367 -53
  33. package/bin/_cctally_transcript.py +8 -7
  34. package/bin/_cctally_tui.py +61 -56
  35. package/bin/_cctally_weekrefs.py +128 -21
  36. package/bin/_lib_account_binding.py +152 -0
  37. package/bin/_lib_account_protocol.py +386 -0
  38. package/bin/_lib_aggregators.py +4 -0
  39. package/bin/_lib_cache_coverage.py +13 -1
  40. package/bin/_lib_cache_report.py +35 -6
  41. package/bin/_lib_cache_writer_lock.py +19 -3
  42. package/bin/_lib_clone_copy.py +142 -0
  43. package/bin/_lib_codex_conversation.py +32 -0
  44. package/bin/_lib_codex_conversation_query.py +297 -49
  45. package/bin/_lib_codex_conversation_watch.py +59 -14
  46. package/bin/_lib_codex_drain_admission.py +214 -0
  47. package/bin/_lib_codex_metadata.py +18 -1
  48. package/bin/_lib_codex_publication_barrier.py +277 -0
  49. package/bin/_lib_codex_rollout_container.py +502 -0
  50. package/bin/_lib_conversation_dispatch.py +3 -0
  51. package/bin/_lib_conversation_query.py +106 -59
  52. package/bin/_lib_conversation_retention.py +1605 -640
  53. package/bin/_lib_cost_provenance.py +53 -2
  54. package/bin/_lib_detector_access_registry.py +3817 -0
  55. package/bin/_lib_doctor.py +714 -69
  56. package/bin/_lib_five_hour.py +126 -0
  57. package/bin/_lib_fmt.py +11 -0
  58. package/bin/_lib_ingest_frontier.py +414 -20
  59. package/bin/_lib_journal.py +141 -10
  60. package/bin/_lib_jsonl.py +5 -4
  61. package/bin/_lib_legacy_spool.py +126 -0
  62. package/bin/_lib_legacy_writer_evidence.py +136 -0
  63. package/bin/_lib_merge_gate.py +231 -86
  64. package/bin/_lib_meter_rate_change.py +10 -0
  65. package/bin/_lib_outcome_quarantine.py +690 -0
  66. package/bin/_lib_perf.py +27 -1
  67. package/bin/_lib_pricing.py +199 -9
  68. package/bin/_lib_pricing_check.py +20 -4
  69. package/bin/_lib_quarantine.py +154 -0
  70. package/bin/_lib_quota_calibration.py +53 -8
  71. package/bin/_lib_quota_copy.py +2 -1
  72. package/bin/_lib_quota_model.py +288 -46
  73. package/bin/_lib_reclaim_planner.py +792 -0
  74. package/bin/_lib_rederive.py +319 -82
  75. package/bin/_lib_segment_summary.py +35 -2
  76. package/bin/_lib_snapshot_cache.py +21 -0
  77. package/bin/_lib_sqlite_close.py +81 -0
  78. package/bin/_lib_statusline_candidates.py +292 -23
  79. package/bin/_lib_subscription_weeks.py +100 -17
  80. package/bin/_lib_tick_stats.py +217 -17
  81. package/bin/_lib_wal_checkpoint.py +1077 -0
  82. package/bin/_lib_write_budget.py +483 -0
  83. package/bin/_lib_write_io.py +703 -0
  84. package/bin/cctally +31 -1
  85. package/dashboard/static/assets/{ConversationsView-CEZ-BUyi.js → ConversationsView-Bc8LCOAZ.js} +1 -1
  86. package/dashboard/static/assets/{DoctorModal-CE1oH_Zy.js → DoctorModal-D--qVBwe.js} +1 -1
  87. package/dashboard/static/assets/ModalRoot-DLPgyAM_.js +1 -0
  88. package/dashboard/static/assets/{ProjectsDrillPanel-B3DZoCeD.js → ProjectsDrillPanel-z53a4v4M.js} +1 -1
  89. package/dashboard/static/assets/{SourceDetailModal-CHp9EgLP.js → SourceDetailModal-Bw0T8W-W.js} +1 -1
  90. package/dashboard/static/assets/{UpdateModal-5iKST6jo.js → UpdateModal-D5AKx4Yw.js} +1 -1
  91. package/dashboard/static/assets/index-BKITk1CT.css +1 -0
  92. package/dashboard/static/assets/{index-Cm0VTxJC.js → index-BXDOzSng.js} +3 -3
  93. package/dashboard/static/assets/{outlineNavigation-DkXGL8J6.js → outlineNavigation-JObY7cUY.js} +1 -1
  94. package/dashboard/static/dashboard.html +2 -2
  95. package/package.json +19 -1
  96. package/dashboard/static/assets/ModalRoot-D5Y8IFUG.js +0 -1
  97. package/dashboard/static/assets/index-Dt4tqf43.css +0 -1
@@ -122,7 +122,8 @@ def claude_card_rate_violations(snapshot) -> list:
122
122
  A closed amount of exactly $0 proves a missing card only while no Claude
123
123
  card prices a used token class at zero. Base rates, any present long-
124
124
  context tier, the derived 1-hour cache-write rate and any Fast multiplier
125
- must all be positive; an empty list is that invariant.
125
+ must all be positive; an empty list is that invariant. A whole-request
126
+ (prompt-length) declaration must also be complete and well formed (#929).
126
127
  """
127
128
  _lib_pricing = _pricing()
128
129
  out = []
@@ -143,23 +144,73 @@ def claude_card_rate_violations(snapshot) -> list:
143
144
  _positive_rate(multiplier) and _positive_rate(tier_input)
144
145
  ):
145
146
  out.append((model, "cache_write_1h_above_200k_tokens"))
147
+ # Not the request hot path, so the unrecognised-field scan runs here
148
+ # too: a card carrying only such a field half-declares the tier (#929).
149
+ if (_lib_pricing._is_whole_request_card(card)
150
+ or _lib_pricing._unrecognised_whole_request_fields(card)):
151
+ out.extend(_whole_request_violations(
152
+ _lib_pricing, model, card, multiplier))
146
153
  stripped = _lib_pricing._strip_anthropic_model_prefix(model)
147
154
  if stripped in fast and not _positive_rate(fast[stripped]):
148
155
  out.append((model, "fast_multiplier"))
149
156
  return out
150
157
 
151
158
 
159
+ def _whole_request_violations(_lib_pricing, model, card, multiplier) -> list:
160
+ """The whole-request (prompt-length) tier's share of the invariant (#929).
161
+
162
+ Every higher rate must be present and positive, the threshold marker must
163
+ be the integer the `_above_100k_tokens` fields name, the card must not mix
164
+ in a marginal `_above_200k_tokens` tier, the derived higher 1-hour write
165
+ rate (higher input x multiplier) must be positive, and the card must carry
166
+ no `_above_100k_tokens` field other than the four recognised ones (the
167
+ selector and `check_table_shapes` refuse such a card too).
168
+ """
169
+ out = []
170
+ for _base, tier in _lib_pricing.CLAUDE_WHOLE_REQUEST_TIER_FIELDS:
171
+ if not _positive_rate(card.get(tier)):
172
+ out.append((model, tier))
173
+ key = _lib_pricing.CLAUDE_WHOLE_REQUEST_THRESHOLD_KEY
174
+ threshold = card.get(key)
175
+ if (key not in card or isinstance(threshold, bool)
176
+ or not isinstance(threshold, int)
177
+ or threshold != _lib_pricing.CLAUDE_WHOLE_REQUEST_THRESHOLD):
178
+ out.append((model, key))
179
+ if any(k.endswith("_above_200k_tokens") for k in card):
180
+ out.append((model, "whole_request_mixed_tiers"))
181
+ if not (_positive_rate(multiplier) and _positive_rate(
182
+ card.get("input_cost_per_token_above_100k_tokens"))):
183
+ out.append((model, "cache_write_1h_above_100k_tokens"))
184
+ for field in _lib_pricing._unrecognised_whole_request_fields(card):
185
+ out.append((model, field))
186
+ return out
187
+
188
+
152
189
  def claude_entry_rates_positive(card, entry, snapshot) -> bool:
153
190
  """True iff every rate that prices one of ``entry``'s tokens is positive.
154
191
 
155
192
  Mirrors `_lib_pricing._calculate_entry_cost` term by term: each token class
156
193
  is priced below the long-context threshold at its base rate and above it at
157
194
  a present tier rate, and a known 1-hour cache-write split prices its share
158
- at the derived input-times-multiplier rate.
195
+ at the derived input-times-multiplier rate. A whole-request card (#929) is
196
+ first resolved to the card the entry's prompt selects.
159
197
  """
160
198
  if card is None:
161
199
  return False
162
200
  _lib_pricing = _pricing()
201
+ # #929: a whole-request card prices this entry at the card its prompt
202
+ # selects, so judge THAT card; every check below then runs unchanged on
203
+ # it. A malformed declaration prices nothing reliably: refuse it.
204
+ if _lib_pricing._is_whole_request_card(card):
205
+ try:
206
+ card = _lib_pricing._select_claude_request_card(
207
+ card,
208
+ input_tokens=int(entry.input_tokens or 0),
209
+ cache_creation_tokens=int(entry.cache_creation_tokens or 0),
210
+ cache_read_tokens=int(entry.cache_read_tokens or 0),
211
+ )
212
+ except ValueError:
213
+ return False
163
214
  threshold = snapshot.tier_thresholds["claude"]
164
215
 
165
216
  def tiered(tokens, base_key, tier_key):